tui
Build terminal output as strings (tables, boxes, bars, trees), colour and style text, and read keys and answers from the user.
import "std:tui"tui is for programs that live in a terminal. It has two halves. The first half returns plain strings you can print or test: tables, boxes, progress bars, bar charts, trees, dividers and padded text. The second half talks to a real terminal: colours, prompts, single key presses, cursor movement and a redrawable screen.
Reach for the string builders in any command-line tool that prints results. Reach for prompts and screens when the program is interactive.
import "std:tui"The first sections build strings, so they run here with their output shown. Colour codes are left out of the recorded output on this page. Prompts and screens need a real terminal, so those examples are compiled but not run.
Tables#
tui.table(headers, rows) draws a table. Each column is as wide as its widest cell.
import "std:tui"
auto headers = ["Name", "Language", "Stars"]
auto rows = [
["osl", "Go", 120],
["compiler", "OSL", 48],
["docs", "Markdown", 7]
]
log tui.table(headers, rows)Cells can be any value, and cells beyond the last header are left out. A row that isn't an array is a TypeError.
Try it: add a fourth column to the headers and a value to each row.
Pad and align text#
tui.pad(text, width, align) makes text a fixed width, aligned "left", "right" or "center". It is the right tool for columns you lay out yourself, such as a receipt.
import "std:tui"
for item in [["tea", "2.50"], ["coffee", "3.10"], ["cake", "12.00"]] (
log tui.pad(item[1], 10, "left") ++ tui.pad(item[2], 8, "right")
)
log "[" ++ tui.pad("mid", 9, "center") ++ "]"Progress and bar charts#
tui.progress(current, total, width) returns a progress bar string, and tui.barChart(data, width, colored) a horizontal chart whose items are [label, value] arrays or {label, value} objects. The largest value fills the width.
import "std:tui"
log tui.progress(0, 100, 20)
log tui.progress(35, 100, 20)
log tui.progress(100, 100, 20)
log tui.barChart([["mon", 3], ["tue", 8], ["wed", 5]], 24, false)Try it: change the second chart value to 20 and watch the bars rescale.
Boxes, frames and dividers#
tui.box(title, content) draws a box around text, and tui.frame(text, width) a double-line frame at least width columns wide. tui.divider(char, width, title) and tui.horizontal(width) draw rules.
import "std:tui"
log tui.box("Status", "all systems go\nuptime: 3 days")
log tui.frame("Welcome", 20)
log tui.divider("=", 30, "Section")
log tui.divider("", 12, "")
log tui.horizontal(8)Trees and grids#
tui.tree(items, prefix) draws nested items with connectors. An item is a value, or an object with a name and children. tui.grid(items, columns) lays items out in equal columns.
import "std:tui"
auto files = [
"README.md",
{name: "src", children: ["main.osl", {name: "util", children: ["strings.osl"]}]},
"osl.json"
]
log tui.tree(files, "")
log tui.grid(["a", "bb", "ccc", "d", "e"], 3)Centre text#
tui.center(text, options) adds spaces to centre each line. Without a width it uses the terminal's, or 80 when output isn't a terminal. Pass width to be exact.
import "std:tui"
log "[" ++ tui.center("title", {width: 15}) ++ "]"
log tui.width() tui.height()Status lines#
tui.status(kind, message) puts a symbol in front of a message: success, error, warning, info or loading. Anything else gets a plain bullet.
import "std:tui"
log tui.status("success", "build finished")
log tui.status("error", "3 tests failed")
log tui.status("warn", "disk almost full")
log tui.status("info", "using cache")
log tui.status("custom", "something else")Colour and style#
tui.color(color, text) colours text with a name (red, green, brightBlue, grey...), a hex string or an {r, g, b} object. tui.bgColor sets the background and tui.style applies bold, dim, underline, reverse, strikethrough and others. They return strings you can join. An unknown name is a ValueError.
The recorded output on this page strips the codes, so the text looks plain here, but it appears coloured in a terminal.
import "std:tui"
log tui.color("red", "error:") tui.style("bold", "disk full")
log tui.color("#ff8800", "orange") tui.bgColor("blue", " on blue ")
log tui.color({r: 10, g: 200, b: 120}, "rgb")
log tui.style("underline", tui.color("cyan", "link"))Pass cellColor to table to colour each cell by its value:
import "std:tui"
log tui.table(["test", "result"], [["parse", "pass"], ["render", "fail"]], {
cellColor: def(any cell) string (
if cell == "fail" return "red"
return "green"
)
})Try it: use "brightBlue" or {r: 255, g: 0, b: 128}.
Ask the user#
Prompts print a question and wait. They are results, because standard input may be closed (IOError). They read from the terminal, so these examples are compiled but not run here.
import "std:tui"
string name = tui.input("Your name: ").unwrapOr("stranger")
bool ok = tui.confirm("Create " ++ name ++ "'s folder").unwrapOr(false)
string secret = tui.password("Token: ").unwrap()
log ok secret.len
string? pick = tui.choose("Pick a colour", ["red", "green", "blue"]).unwrap()
if pick == null ( log "cancelled" ) else ( log "chose" pick )confirm adds [y/N] and treats an empty line as no. choose shows a menu moved with the arrow keys, and returns null when the user cancels with Esc. When input is piped instead of typed, the same functions read plain lines, so scripts can feed them.
Read single keys#
tui.readKey() waits for one key press and returns its name: "a", "enter", "up", "ctrl+c", "f5" and so on. tui.keyPressed() checks without waiting, so you can keep animating while you wait for input.
import "std:tui"
tui.hideCursor()
log "Press q to quit, or any other key."
while true (
string key = tui.readKey().unwrap()
if key == "q" break
log "you pressed" key
)
tui.showCursor()Cursor movement and spinners#
moveTo(x, y) jumps to a cell (1, 1 is the top-left corner), and moveUp, moveDown, moveLeft, moveRight, saveCursor, restoreCursor, clearLine and clearLines redraw part of the output in place. spinner returns the frame to show, which changes every 100 milliseconds.
import "std:tui"
import "std:thread"
for i in 1 to 5 (
tui.clearLine()
tui.write("\r" ++ tui.spinner(false) ++ " working " ++ tui.progress(i, 5, 10))
thread.sleep(200)
)
tui.clearLine()
log tui.spinner(true) "done"Screens#
For full-screen programs, tui.newScreen() returns a buffer the size of the terminal. Set cells, then call render() to draw everything in one write, which avoids flicker. drawPanel draws a titled box at a position.
import "std:tui"
auto screen = tui.newScreen()
tui.clear()
screen.writeCenter(2, "Dashboard")
screen.set(2, 4, "CPU " ++ tui.progress(42, 100, 20))
screen.set(2, 5, "Disk " ++ tui.progress(87, 100, 20))
screen.render()
tui.drawPanel({x: 2, y: 8, width: 30, height: 5}, {title: "Log"})
tui.moveTo(1, screen.height())A screen created with {width: 40, height: 10} has that size instead, which is also how you can test your layout code without a terminal.
import "std:tui"
auto s = tui.newScreen({width: 40, height: 10})
s.set(1, 1, "hello")
s.writeCenter(3, "centred")
log s.width() s.height()What does tui.table(headers, rows) return?
A tiny report
Print a table with the headers Task and Done and two rows, then a progress bar showing 1 of 2 done, 10 wide.
+-------+------+ | Task | Done | +-------+------+ | write | yes | | test | no | +-------+------+ [█████░░░░░] 50%
API reference#
53 members, generated from the std:tui source of OSL 9.3.0.
Functions
tui.Select(any prompt, array options) anyDeprecated. Deprecated: use tui.choose(prompt, choices) instead, which returns null when the user cancels and a result that fails with IOError.
Select clears the screen, lists options with numbers and returns the one whose number is typed, asking again after anything else; "" when options is empty. Throws IOError when standard input is closed first.
tui.barChart(array data, int width, boolean colored) stringbarChart returns a horizontal bar chart of data, whose items are {label, value} objects or [label, value] arrays: a scale line width columns long, then one line per item with its label, a bar scaled so the largest value fills width, and the value. colored draws the bars in green. Throws TypeError for any other item.
tui.bgColor(any color, string text) stringbgColor returns text on a background of color, which is a name or an OSL colour as for tui.color. Throws ValueError for an unknown name or an invalid colour.
tui.box(string title, string content) stringbox returns content, which may have several lines, inside a box drawn with line characters, with title in the top border when it isn't empty.
tui.center(string text, object? options) stringcenter returns text, line by line, with spaces before each line to centre it. Options: width (whole number, default the terminal's width, or 80 when output isn't a terminal) is the width to centre in. Before this release it always centred in 80 columns, whatever the terminal's width; pass {width: 80} to keep that.
tui.choose(string prompt, string[] choices) result<string?, error>choose asks the user to pick one of choices and returns the one picked, or null when they cancel. In a terminal it shows a menu moved with the arrow keys (or number keys), picked with Enter and cancelled with Esc or Ctrl+C; otherwise, such as when input is piped, it lists the choices with numbers and reads the number of the one picked, where 0 cancels. Fails with IOError when standard input is closed first. Throws ValueError when choices is empty.
tui.clear()clear clears the terminal and moves the cursor to the top-left corner.
tui.clearLine()clearLine clears the line the cursor is on.
tui.clearLines(int count)clearLines clears the count lines above the cursor, such as the last count lines printed, and leaves the cursor at the start of the first of them. Throws ValueError for a negative count.
tui.color(any color, string text) stringcolor returns text coloured with color: a name (black, red, green, yellow, blue, magenta or purple, cyan, white, gray or grey, and each with a "bright" prefix such as "brightBlue"; any case) or an OSL colour such as "#ff8800" or {r, g, b}, drawn in 24-bit colour. Throws ValueError for an unknown name or an invalid colour.
tui.confirm(string prompt) result<boolean, error>confirm asks a yes-or-no question: it prints prompt (adding "?" when it doesn't end with one) and " [y/N]: ", and returns true for y or yes and false for n, no or an empty line, in any case, asking again after any other answer. Fails with IOError when standard input is closed first.
tui.divider(string char, int width, string title) stringdivider returns a line of char width columns long with title centred in it when title isn't empty. char "" means "─" and a width of 0 or less means 80. Throws ValueError when char isn't a single one-column character.
tui.drawPanel(object rect, object? options)drawPanel draws a box filling rect {x, y, width, height}, in terminal cells with x and y its top-left cell counted from 1, 1, and blanks its inside. The cursor stays where it was. Options: title (string) is shown centred in the top border, cut to fit. Throws ValueError for a position under 1, a width or height under 2, or a malformed rectangle.
tui.frame(string text, int width) stringframe returns text, which may have several lines, inside a double-line frame at least width columns wide inside.
tui.grid(array items, int columns) stringgrid returns items laid out in columns columns (2 when columns is 0 or less), each cell as wide as the widest item.
tui.height() intheight returns the terminal's height in rows, or 24 when output isn't a terminal.
tui.hideCursor()hideCursor hides the cursor until showCursor.
tui.horizontal(int width) stringhorizontal returns a horizontal line "───" width columns long; a width of 0 or less gives 80.
tui.input(string prompt) result<string, error>input prints prompt and returns the line typed after it, without surrounding whitespace. Fails with IOError when standard input is closed before a line arrives.
tui.interactiveSelect(any prompt, array options) anyDeprecated. Deprecated: use tui.choose(prompt, choices) instead.
interactiveSelect asks the user to pick one of options like tui.choose and returns it, or null when they cancel; "" when options is empty. Throws IOError when standard input is closed first.
tui.keyPressed() booleankeyPressed reports whether a key press is waiting to be read with readKey, without waiting for one. When standard input isn't a terminal it reports whether input is already buffered. Throws UnsupportedError on terminals that can't be checked without waiting (Windows consoles).
tui.moveDown(int n)moveDown moves the cursor down n rows; 0 doesn't move it. Throws ValueError for a negative n.
tui.moveLeft(int n)moveLeft moves the cursor left n columns; 0 doesn't move it. Throws ValueError for a negative n.
tui.moveRight(int n)moveRight moves the cursor right n columns; 0 doesn't move it. Throws ValueError for a negative n.
tui.moveTo(int x, int y)moveTo moves the cursor to column x and row y, counted from 1, 1 at the top-left cell. Throws ValueError for a position under 1.
tui.moveUp(int n)moveUp moves the cursor up n rows; 0 doesn't move it. Throws ValueError for a negative n.
tui.newScreen(object? options) *tui.ScreennewScreen returns an empty screen buffer the size of the terminal (80 x 24 when output isn't a terminal). Options: width and height (whole numbers of 1 or more) set its size instead. Throws ValueError for a size under 1.
tui.pad(string text, int width, string align) stringpad returns text padded with spaces to width columns, aligned "left" (also ""), "right" or "center"; text already that wide is returned as it is. Throws ValueError for another align.
tui.password(string prompt) result<string, error>password prints prompt and reads a line without showing what is typed when standard input is a terminal; otherwise it reads the next line. Returns the text without its line ending. Fails with IOError when standard input is closed first.
tui.progress(number current, number total, int width) stringprogress returns a progress bar such as "[█████░░░░░] 50%" for current out of total, with a bar width columns wide. current is clamped to 0..total, and a total of 0 or less shows 0%. Throws ValueError for a negative width.
tui.readKey() result<string, error>readKey waits for one key press and returns its name: a character such as "a", "A", "?" or "é" (" " for space), "enter", "tab", "backspace", "escape", "up", "down", "left", "right", "home", "end", "insert", "delete", "pageup", "pagedown", "f1" to "f12", "ctrl+a" to "ctrl+z" (Ctrl+C is read as a key, not an interrupt), modified keys such as "ctrl+up", "shift+tab" or "alt+x", or "unknown" for any other sequence. When standard input isn't a terminal it reads the next key from it. Fails with IOError when standard input is closed.
tui.restoreCursor()restoreCursor moves the cursor back to where saveCursor remembered.
tui.rgbBg(any r, any g, any b, any text) stringDeprecated. Deprecated: use tui.bgColor({r: r, g: g, b: b}, text) or tui.bgColor("#rrggbb", text) instead.
rgbBg returns text on the 24-bit background colour r, g, b.
tui.rgbColor(any r, any g, any b, any text) stringDeprecated. Deprecated: use tui.color({r: r, g: g, b: b}, text) or tui.color("#rrggbb", text) instead.
rgbColor returns text in the 24-bit colour r, g, b.
tui.saveCursor()saveCursor remembers the cursor position for restoreCursor.
tui.showCursor()showCursor shows the cursor again after hideCursor.
tui.size() arrayDeprecated. Deprecated: use tui.width() and tui.height() instead.
size returns [width, height] of the terminal, or [80, 24] when output isn't a terminal.
tui.spinner(boolean finished) stringspinner returns the current frame of a spinner, which changes every 100 ms, or "✓" when finished is true. Print it over itself to animate it.
tui.status(string status, string message) stringstatus returns message after a coloured symbol for status: "success", "ok" or "done" (✓), "error", "fail" or "failed" (✗), "warning" or "warn" (⚠), "info" (ℹ) and "loading" or "pending" (⏳), in any case; any other status gets a plain "•".
tui.style(string name, string text) stringstyle returns text in a style: "bold", "dim", "underline", "blink", "reverse" (or "invert"), "hidden" or "strikethrough", in any case. Throws ValueError for an unknown style.
tui.table(array headers, array rows, object? options) stringtable returns headers and rows (an array of arrays of cells) as a table drawn with +, - and |, each column as wide as its widest cell; cells past the last header are left out. Options: cellColor (function) is called with each body cell and returns its colour as for tui.color, and turns on cyan borders and bold headers. Throws TypeError when a row isn't an array.
tui.tableColored(array headers, array rows, any colorFn) stringDeprecated. Deprecated: use tui.table(headers, rows, {cellColor: colorFn}) instead.
tableColored returns a table with cyan borders, bold headers and each cell coloured with the colour name colorFn(cell) returns; an unknown name leaves the cell uncoloured.
tui.tree(array items, string prefix) stringtree returns items drawn as a tree with ├── and └── connectors, each line starting with prefix. An item is a value shown as text, or an object {name, children} whose children array is drawn nested under it.
tui.vertical(int height) string[]vertical returns height copies of "│", one per row; a height of 0 or less gives 10.
tui.width() intwidth returns the terminal's width in columns, or 80 when output isn't a terminal.
tui.write(any value)write prints value to standard output as text, without a newline.
*tui.Screen
screen.clear()clear blanks every cell of the buffer.
screen.height() intheight returns the screen's height in cells.
screen.render()render draws the whole buffer on the terminal from the top-left corner in one write.
screen.set(int x, int y, string text)set writes text into the buffer starting at column x of row y, both counted from 1, 1 at the top-left cell, one character per cell; the parts that fall outside the screen, such as at column 0 or below, are cut off. Call render to show it.
screen.width() intwidth returns the screen's width in cells.
screen.writeCenter(int y, string text)writeCenter writes text centred on row y (counted from 1); a row outside the screen writes nothing.