Module
sysl.term
What a terminal understands: the escape sequences that colour text, emphasise it, and move the cursor about.
import sysl.term
What a terminal understands: the escape sequences that colour text, emphasise it, and move the cursor about.
Every one of these is a const string, and that is the whole design. A string literal is
immortal – it lives in the program’s own image with no owner and no reference count (04) – so
naming forty of them costs nothing at run time and nothing in storage, and a module that has
declared @no_alloc can reach every one. That is why this module declares it too: colouring a
line is exactly what a program with no allocator most wants to do, and a facility it could not use
would be no facility at all.
A sequence is written where the text it affects is written, because that is what it is – a mark in the stream rather than a property of a string:
print(f”{red}{bold}error{reset}: {msg}”)
What is not here, and why
Nothing that asks whether the terminal is a terminal. Whether escapes should be emitted at all
– output redirected to a file, NO_COLOR set, a dumb terminal – is a question about the process
and its environment, so answering it needs posix. This module asks for no capability at all, and
putting the answer here would take that away from every program that only wanted to name a colour.
So the answer lives in sysl.posix.tty, which requires what it needs and leaves this file asking
for nothing – and the namespace is the honest place for it, since isatty and termios are what
it is made of. A hosted program imports both and asks once:
val paint = color()
print(f”\({if paint then red else ""}error\){if paint then reset else “”}: ${msg}”)
The split is what serves both audiences instead of choosing between them, and it shows up in the import, which is honest about what the second one costs.
Nothing that takes a number. Moving the cursor to a row and column means building
ESC [ row ; col H, and building a string is an allocation – which is the one thing the whole
module is arranged to avoid. The sequences below are the ones whose text is fixed. A program that
wants the others has f"\u{1b}[{row};{col}H" and knows what it is spending.
Reading the numbers
These are the SGR parameters, and the arithmetic between them is a real specification rather than a
coincidence: a background is its foreground plus ten, and a bright colour is its ordinary one plus
sixty. TermTests asserts both relations across all eight colours, because a table of forty
constants transcribed by hand has exactly one interesting failure – a wrong number – and it is
invisible in review.
Index
black blink blue bold bright_black bright_blue bright_cyan bright_green bright_magenta bright_red bright_white bright_yellow clear_below clear_line clear_screen clear_to_line_end cyan default_color dim green hidden hide_cursor home italic magenta on_black on_blue on_bright_black on_bright_blue on_bright_cyan on_bright_green on_bright_magenta on_bright_red on_bright_white on_bright_yellow on_cyan on_default on_green on_magenta on_red on_white on_yellow red reset restore_cursor reverse save_cursor show_cursor strike underline white yellow
Constants
black
const black: string = "[30m"
blink
const blink: string = "[5m"
blue
const blue: string = "[34m"
bold
const bold: string = "[1m"
bright_black
const bright_black: string = "[90m"
bright_blue
const bright_blue: string = "[94m"
bright_cyan
const bright_cyan: string = "[96m"
bright_green
const bright_green: string = "[92m"
bright_magenta
const bright_magenta: string = "[95m"
bright_red
const bright_red: string = "[91m"
bright_white
const bright_white: string = "[97m"
bright_yellow
const bright_yellow: string = "[93m"
clear_below
const clear_below: string = "[0J"
From the cursor onwards, which is what a program redrawing the tail of something wants.
clear_line
const clear_line: string = "[2K"
clear_screen
const clear_screen: string = "[2J"
The whole screen, and the whole line the cursor is on. Neither moves the cursor, which is why
clear_screen is nearly always written with home after it.
clear_to_line_end
const clear_to_line_end: string = "[0K"
cyan
const cyan: string = "[36m"
default_color
const default_color: string = "[39m"
The colour a terminal was already using, which is not the same as reset: this ends the colour
and leaves the emphasis where it was.
dim
const dim: string = "[2m"
green
const green: string = "[32m"
hidden
const hidden: string = "[8m"
hide_cursor
const hide_cursor: string = "[?25l"
Hiding it is what stops a full-screen redraw from leaving the cursor skittering across the picture. A program that hides it owns showing it again, including on the way out.
home
const home: string = "[H"
The top left corner.
italic
const italic: string = "[3m"
magenta
const magenta: string = "[35m"
on_black
const on_black: string = "[40m"
on_blue
const on_blue: string = "[44m"
on_bright_black
const on_bright_black: string = "[100m"
on_bright_blue
const on_bright_blue: string = "[104m"
on_bright_cyan
const on_bright_cyan: string = "[106m"
on_bright_green
const on_bright_green: string = "[102m"
on_bright_magenta
const on_bright_magenta: string = "[105m"
on_bright_red
const on_bright_red: string = "[101m"
on_bright_white
const on_bright_white: string = "[107m"
on_bright_yellow
const on_bright_yellow: string = "[103m"
on_cyan
const on_cyan: string = "[46m"
on_default
const on_default: string = "[49m"
on_green
const on_green: string = "[42m"
on_magenta
const on_magenta: string = "[45m"
on_red
const on_red: string = "[41m"
on_white
const on_white: string = "[47m"
on_yellow
const on_yellow: string = "[43m"
red
const red: string = "[31m"
reset
const reset: string = "[0m"
Ends everything: colour, background and every attribute at once. ANSI has no way to end one attribute and leave the others, which is why the two below exist for the common case of wanting a colour back without losing an emphasis.
restore_cursor
const restore_cursor: string = "[u"
reverse
const reverse: string = "[7m"
save_cursor
const save_cursor: string = "[s"
One remembered position – the terminal’s own, so nesting two of these does not work and the second save is the one that is restored.
show_cursor
const show_cursor: string = "[?25h"
strike
const strike: string = "[9m"
underline
const underline: string = "[4m"
white
const white: string = "[37m"
yellow
const yellow: string = "[33m"