std.terminal.widget
What a program shows on a terminal, and what is typed at it, as values:
the keys a terminal’s input is read as (decode), the rows a screen is
drawn as (Frame) and the text that draws them (paint), and a few
widgets, each a value and what a key makes of it. Pure, the same on
every backend: import it by a name, import std.terminal.widget as Widget.
An app (App s r) is a state at the start (start), what a key makes
of a state (step: Continue a new state, or Done a result), and how
a state is shown on a terminal of a Size (view). std.terminal’s
interact runs one on the terminal; to test one without a terminal,
replay app keys gives where it ends, and shownAfter app size keys
the rows it shows.
A Key is Char a rune, Enter, Escape, Tab, BackTab,
Backspace, Delete, ArrowUp, ArrowDown, ArrowLeft,
ArrowRight, Home, End, PageUp, PageDown, Control a letter,
or an unknown Sequence; write them without Widget..
A Frame is rows, each a list of Spans, and where the cursor is,
if shown; a Span is text with a Style (plain, bold, dim and
inverse make one). width, cut, pad and wrap measure and lay
out text by columns, an East Asian wide rune two.
| Widget | Key function | What it is |
|---|---|---|
Menu | menuKey, menuRows | an item picked from a list |
Field | fieldKey | text typed, with a shell’s editing keys |
Choice | choiceKey | one of some values |
status | a status line |
Key
data Key = Char Rune | Enter | Escape | Tab | BackTab | Backspace | Delete | ArrowUp | ArrowDown | ArrowLeft | ArrowRight | Home | End | PageUp | PageDown | Control Rune | Sequence StringA key typed: a rune, or a key of its own, or a control key (Control 'a', Ctrl-A); an escape sequence not known is Sequence, as it came.
Decoded
type Decoded = { key : Key, next : Int }decode
decode : String -> List KeyThe keys a terminal’s input is: its runes, UTF-8, and its control bytes and escape sequences (xterm’s, VT100’s) as the keys they are.
>>> decode "j\r\u{1b}[A\u{1b}[3~\u{1b}OB\t\u{1b}[Z\u{7f}\u{1}é"[Char 'j', Enter, ArrowUp, Delete, ArrowDown, Tab, BackTab, Backspace, Control 'a', Char 'é']>>> decode "\u{1b}\u{1b}[1;5C\u{1b}[200~"[Escape, ArrowRight, Sequence "\u{1b}[200~"]columns
columns : Rune -> IntHow many columns a rune takes on a terminal: two, an East Asian wide one’s or an emoji’s, else one.
>>> List.map columns ['a', 'é', '中', '😀'][1, 1, 2, 2]width
width : String -> IntHow many columns a text takes.
cut
cut : Int -> String -> StringAs much of a text as fits in w columns.
>>> cut 3 "中文字""中"pad
pad : Int -> String -> StringA text cut to w columns, and spaces after it up to them.
>>> pad 5 "ab""ab "wrap
wrap : Int -> String -> List StringA text’s words, put on lines of w columns at most.
>>> wrap 10 "the words of a text, wrapped"["the words", "of a text,", "wrapped"]Style
data Style = Plain | Bold | Dim | Inverse | UnderlineHow a span of text is drawn.
Span
type Span = { text : String, style : Style }A span of a row: its text, and how it is drawn.
Row
type Row = List SpanA row of the screen: its spans, left to right.
Frame
type Frame = { rows : List Row, cursor : Maybe (Int, Int) }A screen: its rows, from the top, and where the cursor is shown, a column and a row from 0 (none: hidden).
Size
type Size = { columns : Int, rows : Int }The size of a terminal, in columns and rows.
plain
plain : String -> SpanA span drawn plainly, and the others.
bold
bold : String -> SpanA span drawn bold.
dim
dim : String -> SpanA span drawn dim.
inverse
inverse : String -> SpanA span drawn inverse, its colours swapped.
rowText
rowText : Row -> StringA row’s text, its styles left out.
texts
texts : Frame -> List StringA screen’s rows’ texts: what it shows, its styles left out (for a test).
fill
fill : Int -> List Row -> List RowThe rows, as many as h: those given, and empty ones after them.
status
status : Int -> String -> RowA row of the width, inverse: a status line.
>>> rowText (status 6 "ok")"ok "paint
paint : Size -> Frame -> StringWhat draws the screen on a terminal of the size, in place of what was
there: from its top left, each row cut to its width and the rest of
its line cleared, as many rows as it has, what is below cleared, and
the cursor put where it is shown (else hidden) — as one update, the
terminal told to show it whole (?2026).
>>> paint { columns = 4, rows = 2 } { rows = [[bold "hello"], [plain "a"], [plain "b"]], cursor = Just (MkPair 1 1) }"\u{1b}[?2026h\u{1b}[?25l\u{1b}[H\u{1b}[0;1mhell\u{1b}[0m\u{1b}[K\r\n\u{1b}[0ma\u{1b}[0m\u{1b}[K\u{1b}[0m\u{1b}[J\u{1b}[2;2H\u{1b}[?25h\u{1b}[?2026l"Menu
type Menu = { at : Int }A list’s selection: which of its items is picked, from 0.
menu
menu : MenuNothing moved yet: the first.
moveBy
moveBy : Int -> Int -> Menu -> MenuThe selection moved by d among n items, no further than the ends.
menuKey
menuKey : Int -> Key -> Menu -> MenuThe selection, after a key, among n items: up and down, a page (10)
up and down, to the first and the last; another key moves nothing.
>>> (menuKey 5 End (menuKey 5 ArrowDown menu)).at4>>> (menuKey 5 PageDown { at = 1 }).at4menuRows
menuRows : Int -> Menu -> List Row -> List RowThe items shown in h rows, the picked one marked and inverse,
scrolled so that it is among them.
>>> List.map rowText (menuRows 2 { at = 2 } [[plain "a"], [plain "b"], [plain "c"]])[" b", "> c"]Field
type Field = { text : List Rune, at : Int }A field’s text, as runes, and where its cursor is among them.
field
field : String -> FieldA field of the text, its cursor at its end.
fieldText
fieldText : Field -> StringA field’s text.
fieldCursor
fieldCursor : Field -> IntThe column the cursor is at, from the field’s start.
fieldKey
fieldKey : Key -> Field -> FieldThe field, after a key: a rune typed at the cursor, a backspace, a delete, the cursor moved (left, right, to the start, to the end), or a control key’s edit; another key changes nothing.
>>> fieldText (List.foldl (\f k -> fieldKey k f) (field "") (decode "ac\u{1b}[Db\u{1b}[F!"))"abc!">>> fieldText (List.foldl (\f k -> fieldKey k f) (field "one two") (decode "\u{17}x"))"one x"Choice
type Choice = { options : List String, at : Int }A choice: the values, and which of them is chosen, from 0.
choice
choice : List String -> ChoiceA choice of the values, the first chosen.
chosen
chosen : Choice -> StringThe value chosen ("" where there is none).
choiceKey
choiceKey : Key -> Choice -> ChoiceThe choice, after a key: the next value (right, space, l), the
one before (left, h), round; another key changes nothing.
>>> chosen (choiceKey ArrowLeft (choice ["esm", "page", "c"]))"c"choiceRow
choiceRow : Bool -> (String -> String) -> Choice -> RowThe values side by side, each as shown writes it, the chosen one
inverse where the choice is the one being set (focused), else
bold.
>>> rowText (choiceRow True (\o -> o) (choice ["a", "b"]))"a b"Step
data Step s r = Continue s | Done rWhat a key makes of an app: its state, to go on with, or what it
came to, r, where it is done.
App
type App s r = { start : s, step : Key -> s -> Step s r, view : Size -> s -> Frame }An app: its state at the start, what a key makes of a state, and how a state is shown on a terminal of a size.
stepAll
stepAll : {s r : Type} -> (Key -> s -> Step s r) -> Step s r -> List Key -> Step s rEach key stepped, in turn, until one is done.
advance
advance : {s r : Type} -> (Key -> s -> Step s r) -> s -> Maybe String -> Step s (Maybe r)What is read from a terminal, stepped: each of its keys in turn,
until one is done; at the end of the input (Nothing), done, with
nothing.
replay
replay : {s r : Type} -> App s r -> List Key -> Either s rThe app, run on the keys: what it came to (Right), or the state
it is in after them (Left).
shownAfter
shownAfter : {s r : Type} -> App s r -> Size -> List Key -> List StringWhat the app shows after the keys (none, where it is done), its rows’ texts.