tui:   Terminal UI for Racket
1 Installation
2 Quick Start
3 API Conventions
3.1 Prefix:   behavior
3.2 Suffix:   side effects
3.3 Rule 1:   put /   format symmetry
3.4 Rule 2:   argument order
3.5 Rule 3:   three color tiers are isomorphic
3.6 Standard API categories
4 Lifecycle
with-tui
with-tui-nobuffer
with-tui-nobuffer-echo
tui-init
tui-exit
tui-init-no-buffer
tui-exit-no-buffer
tui-init-no-buffer-echo
tui-exit-no-buffer-echo
enable-mouse!
disable-mouse!
enable-bracketed-paste!
disable-bracketed-paste!
5 Output
put
put-string
put-bytes
put-byte
put-char
put-newline
put-format-bytes
format-newline
put-at
put-at!
5.1 Cursor
cursor-up
cursor-down
cursor-left
cursor-right
cursor-move
cursor-col
cursor-home
cursor-hide
cursor-show
put-cursor-save
put-cursor-restore
5.2 Screen and lines
screen-clear
screen-clear-below
screen-clear-above
line-clear
line-clear-right
line-clear-left
line-clear-row
buffer-alt-enable
buffer-alt-disable
5.3 Flush mode
set-immediate-mode!
set-buffered-mode!
flush!
6 Colors
put-fg
put-bg
put-rgb-fg
put-rgb-bg
put-rgb-fg-bg
put-256-fg
put-256-bg
put-reset
6.1 Positioned colors
put-fg-at
put-fg-at!
put-bg-at
put-bg-at!
put-rgb-fg-at
put-rgb-fg-at!
put-rgb-bg-at
put-rgb-bg-at!
put-rgb-fg-bg-at
put-rgb-fg-bg-at!
put-256-fg-at
put-256-fg-at!
put-256-bg-at
put-256-bg-at!
6.2 Escape-only variants
put-fg-base
put-bg-base
put-rgb-fg-base
put-rgb-bg-base
put-rgb-fg-bg-base
put-256-fg-base
put-256-bg-base
put-bold
put-dim
put-italic
put-underline
put-blink
put-reverse
7 Styles
7.1 Usage
style-define!
style-apply!
style-reset
style->bytes
put-styled
put-styled-at
put-styled-at!
put-styled-bold
put-styled-bold-at
put-styled-bold-at!
put-styled-dim
put-styled-dim-at
put-styled-dim-at!
put-styled-italic
put-styled-italic-at
put-styled-italic-at!
put-styled-underline
put-styled-underline-at
put-styled-underline-at!
put-styled-blink
put-styled-blink-at
put-styled-blink-at!
put-styled-reverse
put-styled-reverse-at
put-styled-reverse-at!
7.2 Color and attribute constructors
color-fg
color-bg
color256-fg
color256-bg
color-rgb-fg
color-rgb-bg
color-fg*
color-bg*
attr-bold
attr-dim
attr-italic
attr-underline
attr-blink
attr-reverse
7.3 Color mode
use-256color!
use-16color!
use-color-auto!
7.4 Built-in styles
8 Format functions
format-cursor-move
format-cursor-up
format-cursor-down
format-cursor-left
format-cursor-right
format-cursor-col
format-cursor-home
format-cursor-hide
format-cursor-show
format-cursor-save
format-cursor-restore
format-screen-clear
format-screen-clear-below
format-screen-clear-above
format-line-clear
format-line-clear-right
format-line-clear-left
format-line-clear-row
format-buffer-alt-enable
format-buffer-alt-disable
format-reset
format-bold
format-dim
format-italic
format-underline
format-blink
format-reverse
format-content
format-fg
format-bg
format-rgb-fg
format-rgb-bg
format-rgb-fg-bg
format-256-fg
format-256-bg
format-fg-base
format-bg-base
format-rgb-fg-base
format-rgb-bg-base
format-rgb-fg-bg-base
format-256-fg-base
format-256-bg-base
format-styled
format-styled*
format-styled-at
format-styled-at!
format-styled-bold
format-styled-bold-at
format-styled-bold-at!
format-styled-dim
format-styled-dim-at
format-styled-dim-at!
format-styled-italic
format-styled-italic-at
format-styled-italic-at!
format-styled-underline
format-styled-underline-at
format-styled-underline-at!
format-styled-blink
format-styled-blink-at
format-styled-blink-at!
format-styled-reverse
format-styled-reverse-at
format-styled-reverse-at!
format-content-at
format-content-at!
format-fg-at
format-fg-at!
format-bg-at
format-bg-at!
format-rgb-fg-at
format-rgb-fg-at!
format-rgb-bg-at
format-rgb-bg-at!
format-256-fg-at
format-256-fg-at!
format-256-bg-at
format-256-bg-at!
format-rgb-fg-bg-at
format-rgb-fg-bg-at!
9 Input
9.1 Events
read-event
read-event-noblock
event?
mods
no-mods?
->mods
mods->list
key-event
paste-event
mouse-event
resize-event
null-event
other-event
byte->key
normalize-event
9.2 High-level:   build-input and event loops
build-input
9.2.1 Callback arguments
9.2.2 Example
loop-input
loop-input-noblock
loop-input/  stop
loop-input-noblock/  stop
9.3 Low-level:   read-event/  raw
read-event/  raw
read-event-noblock/  raw
classify-byte
event-null?
event-key?
event-utf8?
event-seq?
event-ctrl?
event-alt?
event-mod-seq?
event-resize?
event-up?
event-down?
event-left?
event-right?
event-del?
event-insert?
event-home?
event-end?
event-pageup?
event-pagedown?
event-backtab?
event-mouse?
event-paste?
event-tab?
event-space?
event-backspace?
event-enter?
event-escape?
ctrl->char
alt->char
mod-seq->char
mod-seq->key
event->string
event->byte
mouse-press?
mouse-release?
mouse-move?
mouse-scroll?
mouse-left?
mouse-middle?
mouse-right?
scroll-up?
scroll-down?
mouse-x
mouse-y
get-mouse-pos
mouse-modifiers
get-resize-rows
get-resize-cols
get-resize-size
10 Terminal
terminal?
enter-raw-mode!
exit-raw-mode!
enter-raw-mode-keep-echo!
call-with-terminal-reply
make-stdin-evt
get-window-size
resize-monitor-start
resize-monitor-stop
make-resize-evt
10.1 Platform detection
android?
termux?
termux-home
termux-prefix
11 Cursor state
current-cursor-row
current-cursor-col
set-cursor!
get-cursor
update-cursor!
12 Configuration constants
ESCDELAY
CSI-MAX-BYTES
PASTE-MAX-BYTES
UTF8-READ-TIMEOUT
PASTE-READ-TIMEOUT
13 Complete example
14 Character backend (tui/  char)
14.1 How it works
14.2 Grid output, cursor and attributes
14.3 Scripted input and event loops
14.4 Frames and bounded runs
14.5 Related entry points
15 Implementation notes
9.3.0.10

tui: Terminal UI for Racket🔗ℹ

lu96-wow

 (require tui) package: racket-tui

A small terminal UI library for Linux and Android/Termux: raw-mode terminal control, mouse and true-color support, bracketed paste, and window-resize events.

Linux / Android (Termux). This library binds directly to Linux termios, signalfd and ioctl. Loading tui on other operating systems raises an error, because the FFI symbols do not exist. Only xterm / qterminal have been tested.

    1 Installation

    2 Quick Start

    3 API Conventions

      3.1 Prefix: behavior

      3.2 Suffix: side effects

      3.3 Rule 1: put / format symmetry

      3.4 Rule 2: argument order

      3.5 Rule 3: three color tiers are isomorphic

      3.6 Standard API categories

    4 Lifecycle

    5 Output

      5.1 Cursor

      5.2 Screen and lines

      5.3 Flush mode

    6 Colors

      6.1 Positioned colors

      6.2 Escape-only variants

    7 Styles

      7.1 Usage

      7.2 Color and attribute constructors

      7.3 Color mode

      7.4 Built-in styles

    8 Format functions

    9 Input

      9.1 Events

      9.2 High-level: build-input and event loops

        9.2.1 Callback arguments

        9.2.2 Example

      9.3 Low-level: read-event/raw

    10 Terminal

      10.1 Platform detection

    11 Cursor state

    12 Configuration constants

    13 Complete example

    14 Character backend (tui/char)

      14.1 How it works

      14.2 Grid output, cursor and attributes

      14.3 Scripted input and event loops

      14.4 Frames and bounded runs

      14.5 Related entry points

    15 Implementation notes

1 Installation🔗ℹ

Install from the package index:

raco pkg install tui

or directly from GitHub:

raco pkg install https://github.com/lu96-wow/racket-tui.git

2 Quick Start🔗ℹ

(require tui)
 
(with-tui
 (λ ()
   (screen-clear)
   (cursor-hide)
   (put-rgb-fg 255 100 0 "Hello TUI!")
   (put-at 5 10 "Direct output")
   (sleep 2)))

3 API Conventions🔗ℹ

The whole API follows two naming tables (prefix, suffix) and three rules (put/format symmetry, argument order, three color tiers). Once these are known, most function names and signatures can be derived without looking them up.

3.1 Prefix: behavior🔗ℹ

Prefix

 

Meaning

 

Example

put-

 

Immediate terminal output (flushes by default)

 

put-string

format-

 

Return bytes without outputting; batch with @racket[put-bytes]

 

format-cursor-move

cursor- screen- line- buffer-

 

Cursor / screen / line / alt-buffer operations (immediate)

 

cursor-move

clr-

 

16-color foreground constructor (shorthand for @racket[(color-fg n)])

 

clr-red

bclr-

 

16-color background constructor

 

bclr-blue

attr-

 

SGR attribute constructor (thunk that emits the escape sequence)

 

attr-bold

color- color256- color-rgb-

 

Color constructors (thunks, for @racket[style-define!])

 

color256-fg

style-

 

Style system

 

style-define!

event-

 

Input-event predicates and accessors

 

event-key?

current-

 

Parameters or tracked cursor variables

 

current-cursor-row

3.2 Suffix: side effects🔗ℹ

Suffix

 

Meaning

 

Example

!

 

Side effects: updates the tracked cursor / terminal mode

 

put-at!

?

 

Predicate, returns boolean

 

terminal?

-at

 

Positioned (@racket[row] @racket[col] first); DECSC/DECRC so the tracked cursor is untouched

 

put-fg-at

-at!

 

Positioned and updates the tracked cursor

 

put-fg-at!

-base

 

Escape sequence only: no content, no reset

 

put-fg-base

3.3 Rule 1: put / format symmetry🔗ℹ

Almost every capability has both a put- form (immediate output) and a format- form (returns bytes), with identical arguments:

(put-fg 1 "x")
(put-rgb-fg-at 1 1 255 0 0 "x")
(put-cursor-save)

Exceptions (by design):

3.4 Rule 2: argument order🔗ℹ

  • The content v (string / bytes / char / number) is always the last argument.

  • Positioned functions take row col first.

  • Color arguments come in the middle: n for 16/256 colors; r g b for RGB; fr fg fb br bg bb (foreground then background) for foreground+background.

(put-rgb-fg-bg-at row col fr fg fb br bg bb v)

3.5 Rule 3: three color tiers are isomorphic🔗ℹ

The 16-color, 256-color and RGB tiers are same-named and same-shaped; only the color argument differs (n / n / r g b):

(put-fg n v)           (put-256-fg n v)           (put-rgb-fg r g b v)
(put-fg-at r c n v)    (put-256-fg-at r c n v)    (put-rgb-fg-at r c r g b v)
(put-fg-base n)        (put-256-fg-base n)        (put-rgb-fg-base r g b)

Knowing any one tier, the other two can be derived.

3.6 Standard API categories🔗ℹ

Category

 

Representative functions

Lifecycle

 

with-tui tui-init enable-mouse!

Basic output

 

put put-bytes put-newline

Positioned output

 

put-at put-at!

Cursor

 

cursor-move put-cursor-save

Screen / line / buffer

 

screen-clear line-clear buffer-alt-enable

Colors

 

put-fg put-rgb-fg put-256-fg

Styles

 

style-define! put-styled color-fg

Format (returns bytes)

 

format-fg format-cursor-move

Input

 

read-event (typed event?) build-input loop-input; raw: read-event/raw event-mouse?

Terminal

 

terminal? enter-raw-mode! get-window-size

Platform

 

android? termux? termux-home

Cursor tracking

 

current-cursor-row set-cursor! update-cursor!

Config constants

 

ESCDELAY PASTE-MAX-BYTES

4 Lifecycle🔗ℹ

All three with-tui entry points are plain functions taking a thunk. They use dynamic-wind internally, so the terminal is always restored whether the body returns normally or raises an exception. Exceptions from the body propagate outward; they are never swallowed.

procedure

(with-tui body) → any

  body : (-> any)
Enters raw mode with the alternate screen buffer, runs body, then restores the terminal (raw mode off, alt buffer disabled, mouse and bracketed paste off, cursor shown, colors reset).

procedure

(with-tui-nobuffer body) → any

  body : (-> any)
Like with-tui, but stays in the main screen buffer (no alternate buffer), so output is visible after the program exits.

procedure

(with-tui-nobuffer-echo body) → any

  body : (-> any)
Like with-tui-nobuffer, but keeps terminal echo enabled. Useful when the terminal must display typed input while the program reads keys.

procedure

(tui-init) → void?

Initializes the terminal in raw mode with the alternate screen buffer. Raises an error if the current input port is not a terminal. If initialization fails partway, already-changed terminal state is rolled back before the error is re-raised.

procedure

(tui-exit) → void?

Restores the terminal to its pre-tui-init state. Idempotent: each cleanup step is defensive, so one failing step prints a warning without stopping the remaining cleanup.

procedure

(tui-init-no-buffer) → void?

procedure

(tui-exit-no-buffer) → void?

Like tui-init / tui-exit, but without the alternate screen buffer.

procedure

(tui-init-no-buffer-echo) → void?

procedure

(tui-exit-no-buffer-echo) → void?

Like tui-init-no-buffer, but keeps terminal echo enabled.

procedure

(enable-mouse!) → void?

Enables mouse event tracking (SGR mode).

procedure

(disable-mouse!) → void?

Disables mouse event tracking.

procedure

(enable-bracketed-paste!) → void?

Enables bracketed paste mode.

procedure

(disable-bracketed-paste!) → void?

Disables bracketed paste mode.

Do not call (exit) inside a with-tui body: it terminates the process without running cleanup, leaving the terminal in raw mode. Use a running? flag plus loop-input/stop instead.

5 Output🔗ℹ

All put- functions write to the current output port immediately (one flush per call) by default; use set-buffered-mode! to batch.

procedure

(put v) → void?

  v : any/c
Prints v using ~a formatting.

procedure

(put-string s) → void?

  s : string?

procedure

(put-bytes bs) → void?

  bs : bytes?

procedure

(put-byte b) → void?

  b : byte?

procedure

(put-char c) → void?

  c : char?

procedure

(put-newline) → void?

Prints the terminal newline (CRLF in raw mode, LF otherwise).

procedure

(put-format-bytes part ...) → void?

  part : bytes?
Concatenates byte-string parts (typically values of format- functions) and writes them in one call.

procedure

(format-newline) → bytes?

Returns the terminal newline as a byte string.

procedure

(put-at row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c
Prints v at row×col without disturbing the tracked cursor position (the terminal saves and restores the cursor).

procedure

(put-at! row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c
Like put-at, but updates the tracked cursor position to row×col.

5.1 Cursor🔗ℹ

procedure

(cursor-up n) → void?

  n : exact-nonnegative-integer?

procedure

(cursor-down n) → void?

  n : exact-nonnegative-integer?

procedure

(cursor-left n) → void?

  n : exact-nonnegative-integer?

procedure

(cursor-right n) → void?

  n : exact-nonnegative-integer?

procedure

(cursor-move row col) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?

procedure

(cursor-col n) → void?

  n : exact-nonnegative-integer?

procedure

(cursor-home) → void?

procedure

(cursor-hide) → void?

procedure

(cursor-show) → void?

procedure

(put-cursor-save) → void?

Saves the cursor position (DECSC).

procedure

(put-cursor-restore) → void?

Restores the saved cursor position (DECRC).

5.2 Screen and lines🔗ℹ

procedure

(screen-clear) → void?

procedure

(screen-clear-below) → void?

procedure

(screen-clear-above) → void?

procedure

(line-clear) → void?

procedure

(line-clear-right) → void?

procedure

(line-clear-left) → void?

procedure

(line-clear-row row) → void?

  row : exact-nonnegative-integer?
Clears the whole given row (1-based) without moving the terminal cursor and without changing the tracked cursor position.

procedure

(buffer-alt-enable) → void?

procedure

(buffer-alt-disable) → void?

5.3 Flush mode🔗ℹ

procedure

(set-immediate-mode!) → void?

Every put- call flushes immediately (the default).

procedure

(set-buffered-mode!) → void?

put- calls only buffer output; flush manually with flush!.

procedure

(flush!) → void?

Flushes buffered output.

6 Colors🔗ℹ

Colors come in three flavors: 16-color ANSI, 256-color, and true color (RGB). The put- variants print content in that color and reset after.

procedure

(put-fg n v) → void?

  n : (integer-in 0 15)
  v : any/c

procedure

(put-bg n v) → void?

  n : (integer-in 0 15)
  v : any/c

procedure

(put-rgb-fg r g b v) → void?

  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(put-rgb-bg r g b v) → void?

  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(put-rgb-fg-bg fr fg fb br bg bb v) → void?

  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?
  v : any/c

procedure

(put-256-fg n v) → void?

  n : (integer-in 0 255)
  v : any/c

procedure

(put-256-bg n v) → void?

  n : (integer-in 0 255)
  v : any/c

procedure

(put-reset) → void?

Resets all styling (the put- counterpart of format-reset).

6.1 Positioned colors🔗ℹ

The -at variants print in color at a fixed position. Like put-at, they use DECSC/DECRC so the tracked cursor is untouched; the -at! variants update the tracked cursor position instead.

procedure

(put-fg-at row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(put-fg-at! row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(put-bg-at row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(put-bg-at! row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(put-rgb-fg-at row col r g b v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(put-rgb-fg-at! row col r g b v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(put-rgb-bg-at row col r g b v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(put-rgb-bg-at! row col r g b v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(put-rgb-fg-bg-at row col fr fg fb br bg bb v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?
  v : any/c

procedure

(put-rgb-fg-bg-at! row col fr fg fb br bg bb v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?
  v : any/c

procedure

(put-256-fg-at row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

procedure

(put-256-fg-at! row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

procedure

(put-256-bg-at row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

procedure

(put-256-bg-at! row col n v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

6.2 Escape-only variants🔗ℹ

The -base variants emit only the escape sequence, without content or reset; the attribute variants emit the corresponding SGR sequence:

procedure

(put-fg-base n) → void?

  n : (integer-in 0 15)

procedure

(put-bg-base n) → void?

  n : (integer-in 0 15)

procedure

(put-rgb-fg-base r g b) → void?

  r : byte?
  g : byte?
  b : byte?

procedure

(put-rgb-bg-base r g b) → void?

  r : byte?
  g : byte?
  b : byte?

procedure

(put-rgb-fg-bg-base fr fg fb br bg bb) → void?

  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?

procedure

(put-256-fg-base n) → void?

  n : (integer-in 0 255)

procedure

(put-256-bg-base n) → void?

  n : (integer-in 0 255)

procedure

(put-bold) → void?

procedure

(put-dim) → void?

procedure

(put-italic) → void?

procedure

(put-underline) → void?

procedure

(put-blink) → void?

procedure

(put-reverse) → void?

7 Styles🔗ℹ

Styles are named bundles of color and attribute thunks. Define them once, then apply by name. style-define! registers every style in both a 256-color and a 16-color registry; the active registry is selected by current-registry (see use-color-auto!).

7.1 Usage🔗ℹ

(style-define! 'fancy clr-yellow bclr-blue attr-bold attr-underline)
 
(put-styled 'fancy "Combined style")
(put-styled-at  5 10 'fancy "fixed")
(put-styled-at! 5 10 'fancy "cursor")
 
 
(put-format-bytes
 (format-styled 'title "Title")
 (format-styled 'info "body text"))

The two registries give automatic 256/16-color fallback: plain color-fg/attr-bold register the same value in both, while color-fg* and color-bg* take separate values for each:

(style-define! 'status (color-fg* 46 2) attr-bold)

style->bytes returns the precomputed escape bytes for a style; it is what format-styled uses internally. An undefined style name is a no-op: it produces empty bytes without raising an error.

procedure

(style-define! name spec ...) → void?

  name : symbol?
  spec : procedure?
Registers name from color/attribute thunks such as color-fg, color-bg, attr-bold, or color-fg*.

procedure

(style-apply! name) → void?

  name : symbol?
Applies the style currently registered under name (no-op if unknown).

procedure

(style-reset) → void?

Resets all styling.

procedure

(style->bytes name) → bytes?

  name : symbol?
Returns the escape-sequence bytes for name.

procedure

(put-styled name v) → void?

  name : symbol?
  v : any/c
Prints v in style name, then resets.

procedure

(put-styled-at row col name v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  name : symbol?
  v : any/c

procedure

(put-styled-at! row col name v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  name : symbol?
  v : any/c

procedure

(put-styled-bold v) → void?

  v : any/c

procedure

(put-styled-bold-at row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-bold-at! row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-dim v) → void?

  v : any/c

procedure

(put-styled-dim-at row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-dim-at! row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-italic v) → void?

  v : any/c

procedure

(put-styled-italic-at row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-italic-at! row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-underline v) → void?

  v : any/c

procedure

(put-styled-underline-at row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-underline-at! row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-blink v) → void?

  v : any/c

procedure

(put-styled-blink-at row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-blink-at! row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-reverse v) → void?

  v : any/c

procedure

(put-styled-reverse-at row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(put-styled-reverse-at! row col v) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c
Attribute-styled output: bold, dim, italic, underline, blink, reverse, each with -at (fixed position, cursor untouched) and -at! (updates tracked cursor) variants. The put- counterparts of format-styled-bold and friends.

7.2 Color and attribute constructors🔗ℹ

procedure

(color-fg n) → procedure?

  n : (integer-in 0 15)

procedure

(color-bg n) → procedure?

  n : (integer-in 0 15)

procedure

(color256-fg n) → procedure?

  n : (integer-in 0 255)

procedure

(color256-bg n) → procedure?

  n : (integer-in 0 255)

procedure

(color-rgb-fg r g b) → procedure?

  r : byte?
  g : byte?
  b : byte?

procedure

(color-rgb-bg r g b) → procedure?

  r : byte?
  g : byte?
  b : byte?

procedure

(color-fg* c256 c16) → color-thunk?

  c256 : (integer-in 0 255)
  c16 : (integer-in 0 15)
A two-registry color: uses c256 when the 256-color registry is active, c16 otherwise.

procedure

(color-bg* c256 c16) → color-thunk?

  c256 : (integer-in 0 255)
  c16 : (integer-in 0 15)

procedure

(attr-bold) → void?

procedure

(attr-dim) → void?

procedure

(attr-italic) → void?

procedure

(attr-underline) → void?

procedure

(attr-blink) → void?

procedure

(attr-reverse) → void?

7.3 Color mode🔗ℹ

procedure

(use-256color!) → void?

Selects the 256-color style registry.

procedure

(use-16color!) → void?

Selects the 16-color style registry.

procedure

(use-color-auto!) → void?

Selects the registry based on the COLORTERM / TERM environment variables. Called automatically by tui-init.

7.4 Built-in styles🔗ℹ

The following styles are pre-registered by the library:

  • Basic: 'red 'green 'blue 'yellow 'cyan 'magenta 'white

  • Levels: 'error 'warning 'info 'success

  • Text: 'title 'subtitle 'heading 'border 'border-bold

  • Widgets: 'button 'button-hover 'button-pressed 'button-disabled

  • Menus: 'menu-item 'menu-selected 'menu-key 'menu-shortcut

  • Lists: 'list-item 'list-selected 'list-alternate

  • Dialogs: 'dialog-title 'dialog-body 'dialog-button 'dialog-highlight

  • Status: 'status-bar 'status-good 'status-warning 'status-bad

  • Input: 'input-normal 'input-focus 'input-error

  • Misc: 'cursor 'selection 'scroll-track 'scroll-thumb

8 Format functions🔗ℹ

The format- functions return byte strings without writing anything. They are meant to be collected and written in one batch with put-format-bytes or put-bytes. Each value-taking variant appends content and a style reset, so format-reset is only needed once at the end of a batch.

procedure

(format-cursor-move row col) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?

procedure

(format-cursor-up n) → bytes?

  n : exact-nonnegative-integer?

procedure

(format-cursor-down n) → bytes?

  n : exact-nonnegative-integer?

procedure

(format-cursor-left n) → bytes?

  n : exact-nonnegative-integer?

procedure

(format-cursor-right n) → bytes?

  n : exact-nonnegative-integer?

procedure

(format-cursor-col n) → bytes?

  n : exact-nonnegative-integer?

procedure

(format-cursor-home) → bytes?

procedure

(format-cursor-hide) → bytes?

procedure

(format-cursor-show) → bytes?

procedure

(format-cursor-save) → bytes?

procedure

(format-cursor-restore) → bytes?

procedure

(format-screen-clear) → bytes?

procedure

(format-screen-clear-below) → bytes?

procedure

(format-screen-clear-above) → bytes?

procedure

(format-line-clear) → bytes?

procedure

(format-line-clear-right) → bytes?

procedure

(format-line-clear-left) → bytes?

procedure

(format-line-clear-row row) → bytes?

  row : exact-nonnegative-integer?

procedure

(format-buffer-alt-enable) → bytes?

procedure

(format-buffer-alt-disable) → bytes?

procedure

(format-reset) → bytes?

procedure

(format-bold) → bytes?

procedure

(format-dim) → bytes?

procedure

(format-italic) → bytes?

procedure

(format-underline) → bytes?

procedure

(format-blink) → bytes?

procedure

(format-reverse) → bytes?

procedure

(format-content v) → bytes?

  v : any/c
Converts v to a byte string (bytes pass through, strings become UTF-8, other values use ~a).

procedure

(format-fg n v) → bytes?

  n : (integer-in 0 15)
  v : any/c

procedure

(format-bg n v) → bytes?

  n : (integer-in 0 15)
  v : any/c

procedure

(format-rgb-fg r g b v) → bytes?

  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(format-rgb-bg r g b v) → bytes?

  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(format-rgb-fg-bg fr fg fb br bg bb v) → bytes?

  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?
  v : any/c

procedure

(format-256-fg n v) → bytes?

  n : (integer-in 0 255)
  v : any/c

procedure

(format-256-bg n v) → bytes?

  n : (integer-in 0 255)
  v : any/c

procedure

(format-fg-base n) → bytes?

  n : (integer-in 0 15)

procedure

(format-bg-base n) → bytes?

  n : (integer-in 0 15)

procedure

(format-rgb-fg-base r g b) → bytes?

  r : byte?
  g : byte?
  b : byte?

procedure

(format-rgb-bg-base r g b) → bytes?

  r : byte?
  g : byte?
  b : byte?

procedure

(format-rgb-fg-bg-base fr fg fb br bg bb) → bytes?

  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?

procedure

(format-256-fg-base n) → bytes?

  n : (integer-in 0 255)

procedure

(format-256-bg-base n) → bytes?

  n : (integer-in 0 255)

procedure

(format-styled name v) → bytes?

  name : symbol?
  v : any/c

procedure

(format-styled* name v) → bytes?

  name : symbol?
  v : any/c
Like format-styled, but without the trailing reset.

procedure

(format-styled-at row col name v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  name : symbol?
  v : any/c

procedure

(format-styled-at! row col name v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  name : symbol?
  v : any/c

procedure

(format-styled-bold v) → bytes?

  v : any/c

procedure

(format-styled-bold-at row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-bold-at! row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-dim v) → bytes?

  v : any/c

procedure

(format-styled-dim-at row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-dim-at! row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-italic v) → bytes?

  v : any/c

procedure

(format-styled-italic-at row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-italic-at! row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-underline v) → bytes?

  v : any/c

procedure

(format-styled-underline-at row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-underline-at! row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-blink v) → bytes?

  v : any/c

procedure

(format-styled-blink-at row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-blink-at! row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-reverse v) → bytes?

  v : any/c

procedure

(format-styled-reverse-at row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-styled-reverse-at! row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-content-at row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-content-at! row col v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  v : any/c

procedure

(format-fg-at row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(format-fg-at! row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(format-bg-at row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(format-bg-at! row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 15)
  v : any/c

procedure

(format-rgb-fg-at row col r g b v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(format-rgb-fg-at! row col r g b v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(format-rgb-bg-at row col r g b v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(format-rgb-bg-at! row col r g b v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  r : byte?
  g : byte?
  b : byte?
  v : any/c

procedure

(format-256-fg-at row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

procedure

(format-256-fg-at! row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

procedure

(format-256-bg-at row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

procedure

(format-256-bg-at! row col n v) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  n : (integer-in 0 255)
  v : any/c

procedure

(format-rgb-fg-bg-at row col fr fg fb br bg bb) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?

procedure

(format-rgb-fg-bg-at! row col fr fg fb br bg bb) → bytes?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?
  fr : byte?
  fg : byte?
  fb : byte?
  br : byte?
  bg : byte?
  bb : byte?

9 Input🔗ℹ

9.1 Events🔗ℹ

read-event returns a single normalized event value (event?); the event’s type is determined by its semantics, not by its byte encoding. Every event that carries modifiers uses a mods? struct: there is no positional modifier list and no polymorphic data payload.

procedure

(read-event) → event?

Reads one input event from the terminal, blocking until input arrives. Equivalent to ncurses getch(): the wait is a zero-CPU sync on stdin and resize events.

procedure

(read-event-noblock) → event?

Like read-event, but returns a null-event immediately when no input is available.

procedure

(event? v) → boolean?

  v : any/c
True for any of the event structs below.

struct

(struct mods (ctrl? alt? shift?)
    #:extra-constructor-name make-mods)
  ctrl? : boolean?
  alt? : boolean?
  shift? : boolean?
The modifier keys held during an event.

procedure

(no-mods? m) → boolean?

  m : mods?
True when no modifier is held.

procedure

(->mods m) → mods?

  m : any/c
Coerces a mods?, a positional (list ctrl? alt? shift?), or #f to a mods?.

procedure

(mods->list m) → (list/c boolean? boolean? boolean?)

  m : mods?
Converts a mods? to (list ctrl? alt? shift?).

struct

(struct key-event (key mods)
    #:extra-constructor-name make-key-event)
  key : (or/c char? symbol?)
  mods : mods?
A key press. key is a char? for a character key (printable, control, or multi-byte) or a symbol? for a named key: 'up 'down 'left 'right 'home 'end 'pageup 'pagedown 'insert 'del 'backtab 'tab 'enter 'backspace 'escape. Both byte 0x08 (^H) and 0x7F (DEL) map to 'backspace, so #:backspace fires for either. The Ctrl/Alt implied by the raw ctrl/alt event types are folded into mods.

struct

(struct paste-event (bytes text)
    #:extra-constructor-name make-paste-event)
  bytes : bytes?
  text : string?
Bracketed paste. bytes is the raw payload; text is the UTF-8 lossy decoding (invalid bytes become U+FFFD).

struct

(struct mouse-event (action button x y mods)
    #:extra-constructor-name make-mouse-event)
  action : symbol?
  button : (or/c symbol? #f)
  x : exact-nonnegative-integer?
  y : exact-nonnegative-integer?
  mods : mods?
action is 'press 'release 'move or 'scroll. button is 'left 'middle or 'right for press/release, 'up or 'down (wheel direction) for scroll, and #f for move. x and y are 1-based coordinates.

struct

(struct resize-event (rows cols)
    #:extra-constructor-name make-resize-event)
  rows : exact-positive-integer?
  cols : exact-positive-integer?

struct

(struct null-event ()
    #:extra-constructor-name make-null-event)
No input was available (read-event-noblock).

struct

(struct other-event (type data mods)
    #:extra-constructor-name make-other-event)
  type : symbol?
  data : any/c
  mods : mods?
An event that could not be normalized (for example an unrecognized escape sequence). type and data are the raw low-level values.

procedure

(byte->key b) → (or/c char? symbol?)

  b : byte?
Maps a raw single byte to a key: a named symbol ('tab 'enter 'backspace 'escape) or a char?.

procedure

(normalize-event type data mods) → event?

  type : symbol?
  data : any/c
  mods : (or/c #f (list/c boolean? boolean? boolean?))
Normalizes a raw (values type data mods) triple (as returned by read-event/raw) into an event value.

9.2 High-level: build-input and event loops🔗ℹ

procedure

(build-input [#:key on-key    
  #:text on-text    
  #:paste on-paste    
  #:mouse on-mouse    
  #:resize on-resize    
  #:null on-null    
  #:any on-any    
  #:tab on-tab    
  #:backtab on-backtab    
  #:space on-space    
  #:enter on-enter    
  #:backspace on-backspace    
  #:escape on-escape    
  #:up on-up    
  #:down on-down    
  #:left on-left    
  #:right on-right    
  #:delete on-delete    
  #:insert on-insert    
  #:home on-home    
  #:end on-end    
  #:pageup on-pageup    
  #:pagedown on-pagedown]) → (-> event? any)
  on-key : (or/c (-> (or/c char? symbol?) mods? any) #f) = #f
  on-text : (or/c (-> string? any) #f) = #f
  on-paste : (or/c (-> bytes? any) #f) = #f
  on-mouse : (or/c (-> symbol? (or/c symbol? #f) exact-nonnegative-integer? exact-nonnegative-integer? mods? any) #f)
   = #f
  on-resize : (or/c (-> exact-positive-integer? exact-positive-integer? any) #f)
   = #f
  on-null : (or/c (-> any) #f) = #f
  on-any : (or/c (-> event? any) #f) = #f
  on-tab : (or/c (-> any) #f) = #f
  on-backtab : (or/c (-> any) #f) = #f
  on-space : (or/c (-> any) #f) = #f
  on-enter : (or/c (-> any) #f) = #f
  on-backspace : (or/c (-> any) #f) = #f
  on-escape : (or/c (-> any) #f) = #f
  on-up : (or/c (-> any) #f) = #f
  on-down : (or/c (-> any) #f) = #f
  on-left : (or/c (-> any) #f) = #f
  on-right : (or/c (-> any) #f) = #f
  on-delete : (or/c (-> any) #f) = #f
  on-insert : (or/c (-> any) #f) = #f
  on-home : (or/c (-> any) #f) = #f
  on-end : (or/c (-> any) #f) = #f
  on-pageup : (or/c (-> any) #f) = #f
  on-pagedown : (or/c (-> any) #f) = #f
Builds an event handler from callback keywords. Every keyword is optional; an event without a matching callback falls through to #:any, or is ignored. The returned function has type (-> event? any) and is passed to loop-input or called with (read-event).

Dispatch priority (built in): null > resize > paste > mouse > key. Within a key-event, an unmodified named key first tries its shortcut callback (#:tab, #:up, ...); then a printable character (no Ctrl/Alt) goes to #:text; otherwise the key goes to #:key. A paste tries #:paste, then #:text.

Special keys are checked before the generic character handler: a single-byte special key (tab, enter, escape, backspace) is never delivered to #:text or #:key, and #\space is claimed by #:space (when set) before it can reach #:text. A modified key (for example Ctrl+Up or Ctrl+Space) never matches a shortcut and goes to #:key with its modifiers.

9.2.1 Callback arguments🔗ℹ

All keywords are optional; an event without a matching callback falls through to #:any, or is ignored if #:any is absent.

Keyword

 

Callback

 

Argument types

#:key

 

(key mods)

 

key — (or/c char? symbol?); mods — mods?. Every key not handled by a shortcut or #:text.

#:text

 

(str)

 

str — string?, one printable character (ASCII or UTF-8) or pasted text.

#:paste

 

(bytes)

 

bytes — bytes?, raw pasted content.

#:mouse

 

(action button x y mods)

 

action — symbol?; button — (or/c symbol? #f); x y — exact-nonnegative-integer?; mods — mods?.

#:resize

 

(rows cols)

 

rows cols — exact-positive-integer?.

#:null

 

()

 

No input available (noblock loop).

#:any

 

(ev)

 

ev — event?, fallback for every unhandled event.

#:tab

 

()

 

Tab, no arguments

#:backtab

 

()

 

Shift+Tab, no arguments

#:space

 

()

 

Space, no arguments

#:enter

 

()

 

Enter, no arguments

#:backspace

 

()

 

Backspace, no arguments

#:escape

 

()

 

Escape, no arguments

#:up

 

()

 

Arrow up, no arguments

#:down

 

()

 

No arguments

#:left

 

()

 

No arguments

#:right

 

()

 

No arguments

#:delete

 

()

 

Delete, no arguments

#:insert

 

()

 

No arguments

#:home

 

()

 

No arguments

#:end

 

()

 

No arguments

#:pageup

 

()

 

No arguments

#:pagedown

 

()

 

No arguments

The shortcut callbacks only fire for an unmodified named key. A modified named key (for example Ctrl+Up) always goes to #:key with its mods?.

9.2.2 Example🔗ℹ

(define handler
  (build-input
    #:text   (lambda (s)   (insert-text s))
    #:key    (lambda (key mods)
               (cond [(and (char? key) (mods-ctrl? mods))
                      (printf "Ctrl+~a\n" key)]
                     [else (printf "key ~a~a\n"
                                   (if (mods-alt? mods) "Alt+" "")
                                   key)]))
    #:up     (lambda ()    (cursor-up 1))
    #:enter  (lambda ()    (newline))
    #:mouse  (lambda (action button x y mods)
               (printf "mouse ~a ~a (~a,~a) ctrl=~a\n"
                       action button x y (mods-ctrl? mods)))
    #:resize (lambda (rows cols) (printf "~ax~a\n" rows cols))
    #:any    (lambda (ev)  (printf "unhandled: ~a\n" ev))))

syntax

(loop-input handler ...)

Reads events forever and calls each handler (a (-> event? any) function) with the event. Macro that expands to a loop.

syntax

(loop-input-noblock handler ...)

syntax

(loop-input/stop stop-expr handler ...)

Like loop-input, but re-evaluates stop-expr after each event and stops when it is true.

syntax

(loop-input-noblock/stop stop-expr handler ...)

9.3 Low-level: read-event/raw🔗ℹ

The raw byte-level parser remains available for terminal debugging or custom protocols. It returns a polymorphic triple; prefer read-event in application code.

procedure

(read-event/raw)

 → 
symbol?
(or/c bytes? list? pair?)
(or/c #f (list/c boolean? boolean? boolean?))
Reads one raw event, blocking. Returns (values type data mods): type is an event-type symbol, data is bytes? for keys/paste, list? for mouse, or (cons/c ...) for resize, and mods is #f or (list ctrl? alt? shift?).

procedure

(read-event-noblock/raw)

 → 
symbol?
(or/c bytes? list? pair?)
(or/c #f (list/c boolean? boolean? boolean?))
Like read-event/raw, but returns immediately with type 'null when no input is available.

procedure

(classify-byte b) → (or/c 'ctrl 'escape 'utf8 'key)

  b : byte?
Classifies the first byte of an input sequence. The single-byte special keys (TAB, LF, CR, ESC, Backspace) are excluded before the generic control range, so they reach their dedicated handling instead of being treated as a Ctrl+letter. This ordering is what makes 'backspace work for both 0x08 and 0x7F.

procedure

(event-null? type) → boolean?

  type : symbol?

procedure

(event-key? type) → boolean?

  type : symbol?

procedure

(event-utf8? type) → boolean?

  type : symbol?

procedure

(event-seq? type) → boolean?

  type : symbol?

procedure

(event-ctrl? type) → boolean?

  type : symbol?

procedure

(event-alt? type) → boolean?

  type : symbol?

procedure

(event-mod-seq? type) → boolean?

  type : symbol?

procedure

(event-resize? type) → boolean?

  type : symbol?

procedure

(event-up? type) → boolean?

  type : symbol?

procedure

(event-down? type) → boolean?

  type : symbol?

procedure

(event-left? type) → boolean?

  type : symbol?

procedure

(event-right? type) → boolean?

  type : symbol?

procedure

(event-del? type) → boolean?

  type : symbol?

procedure

(event-insert? type) → boolean?

  type : symbol?

procedure

(event-home? type) → boolean?

  type : symbol?

procedure

(event-end? type) → boolean?

  type : symbol?

procedure

(event-pageup? type) → boolean?

  type : symbol?

procedure

(event-pagedown? type) → boolean?

  type : symbol?

procedure

(event-backtab? type) → boolean?

  type : symbol?

procedure

(event-mouse? type) → boolean?

  type : symbol?

procedure

(event-paste? type) → boolean?

  type : symbol?
Predicates on the raw event type.

procedure

(event-tab? type data) → boolean?

  type : symbol?
  data : bytes?

procedure

(event-space? type data) → boolean?

  type : symbol?
  data : bytes?

procedure

(event-backspace? type data) → boolean?

  type : symbol?
  data : bytes?

procedure

(event-enter? type data) → boolean?

  type : symbol?
  data : bytes?

procedure

(event-escape? type data) → boolean?

  type : symbol?
  data : bytes?
Predicates that also check the key byte in data.

procedure

(ctrl->char data) → (or/c char? #f)

  data : bytes?
Returns the letter of a Ctrl+letter raw event.

procedure

(alt->char data) → (or/c char? #f)

  data : bytes?
Returns the character of an Alt+character raw event.

procedure

(mod-seq->char data) → (or/c char? #f)

  data : bytes?
Returns the character of a modifyOtherKeys raw event, or #f.

procedure

(mod-seq->key data) → (or/c symbol? #f)

  data : bytes?
Returns the navigation-key symbol of a modified key raw event, or #f.

procedure

(event->string data) → string?

  data : bytes?
Decodes UTF-8 raw event data to a string.

procedure

(event->byte data) → (or/c byte? #f)

  data : bytes?
Extracts a single byte from raw event data.

procedure

(mouse-press? detail) → boolean?

  detail : list?

procedure

(mouse-release? detail) → boolean?

  detail : list?

procedure

(mouse-move? detail) → boolean?

  detail : list?

procedure

(mouse-scroll? detail) → boolean?

  detail : list?

procedure

(mouse-left? detail) → boolean?

  detail : list?

procedure

(mouse-middle? detail) → boolean?

  detail : list?

procedure

(mouse-right? detail) → boolean?

  detail : list?

procedure

(scroll-up? detail) → boolean?

  detail : list?

procedure

(scroll-down? detail) → boolean?

  detail : list?
Predicates on the raw mouse detail list.

procedure

(mouse-x detail) → exact-nonnegative-integer?

  detail : list?

procedure

(mouse-y detail) → exact-nonnegative-integer?

  detail : list?

procedure

(get-mouse-pos detail) → 
exact-nonnegative-integer?
exact-nonnegative-integer?
  detail : list?

procedure

(mouse-modifiers detail) → (list/c boolean? boolean? boolean?)

  detail : list?

procedure

(get-resize-rows data) → exact-positive-integer?

  data : (cons/c exact-positive-integer? exact-positive-integer?)

procedure

(get-resize-cols data) → exact-positive-integer?

  data : (cons/c exact-positive-integer? exact-positive-integer?)

procedure

(get-resize-size data) → 
exact-positive-integer?
exact-positive-integer?
  data : (cons/c exact-positive-integer? exact-positive-integer?)

10 Terminal🔗ℹ

procedure

(terminal?) → boolean?

True when the current input port is a terminal (isatty).

procedure

(enter-raw-mode!) → void?

Turns off canonical mode and echo, disables signal generation, and enables extended input processing. Used internally by tui-init.

procedure

(exit-raw-mode!) → void?

Restores the saved terminal state.

procedure

(enter-raw-mode-keep-echo!) → void?

Raw mode but keeps echo on. Used internally by tui-init-no-buffer-echo.

procedure

(call-with-terminal-reply thunk) → any

  thunk : (-> any)
Runs thunk with a timeout such that a terminal reply (for example from a DSR query) can be read without hanging.

procedure

(make-stdin-evt) → evt?

A synchronizable event that is ready when stdin has input.

procedure

(get-window-size [fd]) → 
(or/c exact-positive-integer? #f)
(or/c exact-positive-integer? #f)
  fd : exact-integer? = 1
Returns the terminal size as (values rows cols), or (values #f #f) if the ioctl fails. fd defaults to stdout.

procedure

(resize-monitor-start) → void?

Starts a background thread watching for SIGWINCH; resizes are reported as 'resize events by read-event.

On Linux the monitor verifies (via /proc/self/task/*/status) that SIGWINCH is blocked in every OS thread before starting. Android/Termux SELinux denies app domains proc:file reads, so that verification is skipped there (or when TUI_RESIZE_PROC_CHECK is "0"); set it to "1" to force the check.

procedure

(resize-monitor-stop) → void?

Stops the resize monitor.

procedure

(make-resize-evt) → evt?

A synchronizable event that is ready when the terminal has been resized.

10.1 Platform detection🔗ℹ

procedure

(android?) → boolean?

True when running on Android. Racket BC reports 'android for (system-type 'os*) under __ANDROID__ (Racket CS reports 'linux).

procedure

(termux?) → boolean?

True inside Termux. Detected from TERMUX_VERSION / TERMUX_APP__PACKAGE_NAME, falling back to a com.termux path in PREFIX or HOME.

procedure

(termux-home) → (or/c string? #f)

The Termux home directory (HOME), or #f when not in Termux. Useful for config/temp files; it cannot replace /proc.

procedure

(termux-prefix) → (or/c string? #f)

The Termux prefix (PREFIX), or #f when not in Termux.

11 Cursor state🔗ℹ

The library tracks the cursor position in two parameters; -at functions update it.

value

current-cursor-row : exact-nonnegative-integer?

The tracked cursor row (1-based).

value

current-cursor-col : exact-nonnegative-integer?

The tracked cursor column (1-based).

procedure

(set-cursor! row col) → void?

  row : exact-nonnegative-integer?
  col : exact-nonnegative-integer?

procedure

(get-cursor) → 
exact-nonnegative-integer?
exact-nonnegative-integer?

procedure

(update-cursor!) → 
exact-nonnegative-integer?
exact-nonnegative-integer?
Sends a DSR query (ESC[6n) and parses the terminal’s reply to refresh the tracked cursor position.

12 Configuration constants🔗ℹ

value

ESCDELAY : real?

Timeout between bytes of an escape sequence (seconds), used to distinguish a lone ESC key press from a control sequence. Default 0.05.

value

CSI-MAX-BYTES : exact-positive-integer?

Maximum CSI sequence length before truncation. Default 32.

value

PASTE-MAX-BYTES : exact-positive-integer?

Maximum bracketed-paste length. Default 1048576 (1 MiB).

value

UTF8-READ-TIMEOUT : real?

Timeout for reading UTF-8 continuation bytes. Default 0.5.

value

PASTE-READ-TIMEOUT : real?

Timeout between bytes while reading a paste. Default 1.0.

13 Complete example🔗ℹ

(require tui)
 
(define (draw-ui)
  (define buffer
    (bytes-append
     format-screen-clear
     (format-cursor-move 0 0)
     (format-rgb-fg 255 255 0) #"=== TUI Demo ===" format-reset
     (format-cursor-move 2 0)
     (format-rgb-fg 0 255 0) #"Press 'q' to quit" format-reset
     (format-cursor-move 4 0)
     (format-rgb-fg 255 0 0) #"Hello, TUI!" format-reset
     (format-cursor-move 6 0)
     (format-256-fg 46 "UTF-8 support: 你好世界") format-reset))
  (put-bytes buffer))
 
(with-tui
 (λ ()
   (cursor-hide)
   (define running? #t)
   (define handler
     (build-input
      #:key (lambda (key mods)
               (when (eqv? key #\q)
                 (set! running? #f)))))
   (define (render-and-handle ev)
     (handler ev)
     (draw-ui))
   (loop-input/stop (not running?) render-and-handle)))

14 Character backend (tui/char)🔗ℹ

tui/char is a drop-in mirror of the whole API whose output goes to an in-memory character grid instead of the terminal. It renders each frame as plain text (no escape codes), which makes it practical for AI agents, tests and CI to inspect UI state without a TTY. Switch backends by the require path only; call sites stay unchanged. Unlike tui, tui/char does not load termios or FFI, so it runs on any platform or sandbox.

(require tui)
(require tui/char)
(require tui/char)
 
(parameterize ([current-screen-size (cons 6 30)])
 (with-tui
  (lambda ()
    (put-bytes
     (bytes-append
      format-screen-clear
      (format-cursor-move 1 1)
      (format-rgb-fg 255 255 0 "=== TUI Demo ===")
      (format-cursor-move 3 1)
      (format-256-fg 46 "count = 42")))
    (displayln (char-frame)))))

prints a plain-text layout:

=== TUI Demo ===

 

count = 42

14.1 How it works🔗ℹ

Every output path in base funnels through format-* (which produces ANSI bytes) and put-* (which writes them). The character backend keeps the same API but substitutes two leaves:

  • format-* returns an op (or op sequence) instead of ANSI bytes, and

  • put-* applies those ops to the character grid.

Because the semantic information is available at the formatter, there is no ANSI parsing on the main path. bytes-append is shadowed (module-locally) by an op concatenator so the documented batch idiom keeps working verbatim:

(bytes-append format-screen-clear (format-cursor-move 1 1) "hi")

An ANSI parser (make-ansi-parser) is retained only as a test oracle: the same UI rendered through ops and through ANSI bytes must agree.

14.2 Grid output, cursor and attributes🔗ℹ

  • (char-frame) returns the current screen as plain text.

  • (screen->text g) / (screen->lines g) render a given grid; #:trim-right? defaults to #t.

  • (screen-ref g row col) returns a cell; cell-text, cell-fg, cell-bg and cell-attrs read it. (style->string c) renders a readable style such as "fg#1 bold".

  • (screen-current-style g) reports the SGR state that the next character would carry.

  • (screen-styled-cells g) lists all non-default cells; (screen-attr-ranges g) compresses attributes into per-row ranges.

  • (get-cursor) returns (row, col) 1-based (as in base); (screen-cursor g) returns 0-based.

14.3 Scripted input and event loops🔗ℹ

Input comes from a script queue rather than the terminal. (char-input-push! spec ...) accepts an event struct, a character, a named-key symbol such as 'enter, a (key mods) pair, or a raw (type data mods) tuple.

(read-event) blocks by default (like the real terminal), so a loop does not spin when there is no input; (read-event-noblock) is the non-blocking variant. Call (char-input-close!) when the script ends; (read-event) then returns a null-event and (char-input-exhausted?) can be used as the stop condition. Both build-input and the loop-input* macros are provided with the same signatures as tui.

14.4 Frames and bounded runs🔗ℹ

flush! triggers current-frame-hook with the current grid, but only when the rendered character grid actually changed (current-frame-dedup?, on by default), so an idle loop does not keep emitting frames. (screen-frame-log-enable! path) appends every frame as plain text to a file; (screen-frame-count) and (screen-frame-log-disable!) complete the API.

For deterministic debugging, char-run processes a finite list of events, rendering once per event and returning the sequence of frame strings (at most one frame per event plus the initial frame), so headless runs never flood output:

(char-run (list #\+ #\+ #\-)
          #:handle (lambda (ev) ...)
          #:render (lambda () ...)
          #:rows 24 #:cols 80)

14.5 Related entry points🔗ℹ

  • (with-tui thunk) plus (parameterize ([current-screen-size (cons 24 80)]) ...) — sized session.

  • current-screen, (the-screen), (current-screen-size) — grid session state.

  • (screen-size g), (screen-cursor-cell g), (screen-style-at g row col) — queries.

The full reference lives in base-char/README.md in the source repository.

15 Implementation notes🔗ℹ

The termios struct layout and flag constants in base/terminal/base.rkt are hardcoded (previously generated by compiling a C program). The values come from the kernel’s asm-generic/termbits.h and are identical across mainstream Linux architectures (x86, arm, aarch64, riscv, ppc, mips, sparc). TERMIOS-SIZE is fixed at 60, which is safe for both glibc and musl. The only exception is the alpha architecture, which is not supported.