The Machines · Graphics & interaction

vt100

Terminal Escape Codes — machines/vt100.shoddy

the vt100 machine's icon

Summary

vt100 lets a Shoddy program boss a terminal around: move the cursor to an exact spot, paint text in bold or underline, clear the screen, scroll a region, and so on. It does this the way terminals have always been told what to do — by printing short, invisible escape sequences mixed in among your ordinary text. An escape sequence is just a string that starts with one special "escape" character. The terminal spots it, quietly carries out the instruction, and prints none of it. Every word in this machine is a little factory that builds one such string and hands it back to you. Nothing here touches the screen itself — you glue the pieces together with & and hand the finished string to Print when you're ready. There's also one word going the other way: EvalKey takes a raw keystroke sequence the terminal sent — an arrow key, a function key — and tells you which key it was.

A Brief History of the VT100

In 1978, Digital Equipment Corporation shipped a video terminal called the VT100: a keyboard and a green-on-black screen that plugged into a big shared computer somewhere down the hall. It couldn't compute anything itself. Its whole job was to show whatever characters the computer sent down the wire, and to send back whatever the person typed. The clever part was that the same wire had to carry two kinds of thing — ordinary text to be displayed, and commands like "move the cursor to the top" or "start underlining." The trick was to reserve one otherwise-unused character, the escape character (value 27), as a flag. Earlier terminals had used it, and by then it was being written down as a standard. Plain text prints as itself. But the moment the terminal sees an escape, it stops and reads the next few bytes as an instruction instead of printing them. Most of those instructions began with escape followed by an opening bracket — a pairing called the Control Sequence Introducer, or CSI. That ESC [ is the seed nearly every word in this machine grows from.

The VT100 was a runaway success, and its particular set of escape sequences was written into a standard — ANSI X3.64 in the United States, and its international twin ECMA-48 — so that other makers could build terminals that spoke the same language. That is the quiet miracle worth pausing on: the VT100 itself is a museum piece, long gone, and yet its language never died. Every terminal window you have ever opened is pretending to be a VT100, or something descended from one — the one on your Mac, the one in Linux, the black window in Windows, the shell inside your code editor. When a modern program prints coloured text or draws a progress bar in place, it is sending very nearly the same bytes a 1978 DEC terminal would have understood. Learn these codes and you have learned a language that has outlived the hardware it was invented for by nearly fifty years.

The VT100 also carried its own past on its back. Its predecessor, the VT52, spoke a shorter, simpler dialect, and the VT100 could switch into a compatibility mode to keep old software happy. That is why this machine has a whole family of Vt52… words alongside the modern ones. They're a fossil layer, meaningful only once the terminal has actually been switched into VT52 mode — but they're part of the real story, so they're here.

Why It's Useful

A plain Print can only do one thing: drop a line of text at the bottom and shove everything else up. That's fine for a list of numbers. But it's no way to build anything that feels alive — a game board that redraws in place, a status line pinned to the top, a menu you can highlight, a progress bar that fills without scrolling the world away. All of that needs two powers a bare terminal already has but won't give you through Print alone: putting the cursor exactly where you want it, and changing how the next characters look. vt100 hands you both, as readable words. Instead of memorising that "go to row 5, column 10" is the cryptic string ESC [ 5 ; 10 H, you write CursorPos(5, 10) and let the machine spell it out. Reach for it whenever a text program wants to draw rather than just scroll — a dashboard, a board game, a full-screen editor, anything that should look put-together rather than printed one line at a time.

User's Guide

The rhythm of using vt100 is always the same: build a string, then print it. Each word returns a small chunk of escape codes; you stitch the chunks together with &, drop your actual text in among them, and hand the whole thing to Print once.

Include "vt100.shoddy"

Def Main()
    ' Wipe the screen, jump to the top-left, greet in bold.
    Print(ClearScreen() & CursorHome() & Bold() & "READY" & AttrsOff())

    ' Put a label at row 5, column 10, underlined.
    Print(CursorPos(5, 10) & Underline() & "SCORE: 0" & AttrsOff())

    ' Decode a raw keystroke captured from the terminal.
    Let k = EvalKey(Esc() & "OA")          ' the Up arrow, application mode
    Select Case k
        Case KeyUp()
            Print("moving up")
        Case Else
            Print("some other key")

A few things worth keeping in mind:

Under the Hood: How the Codes Are Built

You don't need any of this to use the machine — it's here for the curious, and for anyone reading the raw bytes in a hex viewer.

It all comes down to one builtin. The whole machine is built on Chr, which turns a number into the single character with that code. The escape character is Chr(27), wrapped up as Esc; nearly everything else starts from Csi, which is just Esc() & "[" — the Control Sequence Introducer. From there each word is a one-liner. Bold is Csi() & "1m", and ClearScreen is Csi() & "2J". The words that take arguments splice the numbers in with Str, so CursorPos(row, col) is Csi() & Str(row) & ";" & Str(col) & "H". There is no state, no cleverness — just string-building over Chr.

A handful of codes were reconstructed, not copied. This machine was transcribed from an old escape-code reference table, and a few of that table's rows disagree with the well-established VT100/VT52 protocol. Where they conflict, the machine follows the standard and says so in a comment. RequestStatus and RequestCursorPos use the proper CSI form (ESC [ 5 n, ESC [ 6 n) even though the table abbreviated them. And Vt52CursorPos was rebuilt from the real VT52 spec, adding the Y introducer and the +32 offset each coordinate needs — which is why it addresses row 0, column 0 as ESC Y followed by two spaces. If you're driving a real terminal or emulator, sanity-check these against it.

Some words share their exact bytes with others — on purpose. Both the VT100 and the older VT52 reuse short escape sequences, so the same raw bytes can mean different things depending on which mode the terminal is in. Vt52CursorLeft and IndexDown are both ESC D; Vt52CursorHome and TabSet are both ESC H. That collision is a genuine property of the protocol, not a slip in this file — which mode the terminal is actually in decides what the bytes do.

Names dodge a couple of landmines. The reverse-video attribute is called ReverseVideo, not Reverse, because Reverse is a core builtin (it flips lists and arrays). Shadowing it here would break every other machine and program that includes this file. Likewise the key constructors are named KeyUp, KeyDown and so on rather than the plainer names keys uses, so the two machines can be included side by side.

The return path: EvalKey. Going from terminal to host, EvalKey is one big Select Case over the raw string. It recognises both the normal and application-mode forms of every arrow and keypad key and returns a Vt100Key. One honest limitation: in numeric keypad mode a keypad digit sends the very same byte as the matching top-row digit. So a bare "5" is classified as KeyPad("5") by convention — the byte alone can't prove it came from the keypad rather than the number row. Anything EvalKey doesn't recognise comes back as KeyUnknown(raw), handing you the original string so nothing is ever silently lost.

Word Reference

Every word, plus the Vt100Key type

Building blocks

WordDescription
Esc()The escape character itself, Chr(27) — the flag that tells a terminal "an instruction follows." The root everything else is grown from.
Csi()The Control Sequence Introducer, Esc() & "[". Nearly every ANSI-style code below starts here.

Cursor movement

WordDescription
CursorUp(n)Move the cursor up n rows.
CursorDown(n)Move the cursor down n rows.
CursorRight(n)Move the cursor right n columns.
CursorLeft(n)Move the cursor left n columns.
CursorHome()Jump to the top-left corner (row 1, column 1).
CursorPos(row, col)Jump to an exact spot. Rows and columns are 1-based; row first, then column.
HvHome()The same as CursorHome — the source table's alias, using the final byte f instead of H. Kept as its own word because the table lists it separately.
HvPos(row, col)The f-byte alias of CursorPos; same effect.
IndexDown()Move down one line, scrolling the screen if already at the bottom (ESC D).
ReverseIndex()Move up one line, scrolling the screen if already at the top (ESC M).
NextLine()Move to the start of the next line (ESC E).
SaveCursor()Remember the current cursor position (and attributes) so RestoreCursor can return to it.
RestoreCursor()Jump back to the position last stored by SaveCursor.

Erasing

WordDescription
ClearEol()Clear from the cursor to the end of the line.
ClearBol()Clear from the start of the line up to the cursor.
ClearLine()Clear the whole line the cursor is on.
ClearEos()Clear from the cursor to the end of the screen.
ClearBos()Clear from the top of the screen down to the cursor.
ClearScreen()Clear the entire screen.

Character attributes (SGR)

WordDescription
AttrsOff()Turn all attributes back off — back to plain text. End every highlighted run with this.
Bold()Start bold (bright) text.
LowIntensity()Start dim (low-intensity) text.
Underline()Start underlined text.
Blink()Start blinking text.
ReverseVideo()Swap foreground and background (reverse video). Named ReverseVideo to avoid clashing with the Reverse builtin.
Invisible()Start hidden (invisible) text.

Scrolling

WordDescription
SetScrollRegion(top, bottom)Confine scrolling to the rows from top to bottom; the rest of the screen stays put — handy for a fixed header or footer.

Tabs

WordDescription
TabSet()Set a tab stop at the current column (ESC H).
TabClear()Clear the tab stop at the current column.
TabClearAll()Clear every tab stop.

Double-width and double-height lines

WordDescription
DoubleHeightTop()Make this line the top half of a double-height line.
DoubleHeightBottom()Make this line the bottom half of a double-height line.
SingleWidthHeight()Return the line to normal single width and height.
DoubleWidthHeight()Make this line double width and single height.

Modes (set / reset)

WordDescription
SetNewlineMode()Newline mode: Enter sends both carriage return and line feed.
SetLinefeedMode()Line feed mode: Enter sends line feed only.
SetCursorKeyApp()Put the arrow keys in application mode (they send the ESC O forms).
SetCursorKeyNormal()Put the arrow keys back in normal (cursor) mode.
Set132Cols()Switch to 132-column display.
Set80Cols()Switch to 80-column display.
SetSmoothScroll()Smooth (gradual) scrolling.
SetJumpScroll()Jump (instant) scrolling.
SetReverseScreen()Reverse the whole screen (dark on light).
SetNormalScreen()Return the screen to normal (light on dark).
SetOriginRelative()Make cursor positions relative to the scrolling region.
SetOriginAbsolute()Make cursor positions relative to the whole screen.
SetAutoWrap()Turn on auto-wrap: text past the last column continues on the next line.
ResetAutoWrap()Turn off auto-wrap.
SetAutoRepeat()Turn on key auto-repeat (holding a key repeats it).
ResetAutoRepeat()Turn off key auto-repeat.
SetInterlace()Turn on interlaced display.
ResetInterlace()Turn off interlaced display.
SetVt52Mode()Drop the terminal into VT52 compatibility mode — after this it no longer understands CSI codes, only the short Vt52… ones.
SetAnsiMode()Climb back out of VT52 mode into ANSI mode (ESC <). This is a VT52-side command, since a terminal in VT52 mode won't parse CSI at all.

Keypad mode

WordDescription
SetAltKeypad()Application keypad mode: keypad keys send distinct ESC O codes rather than plain digits.
SetNumKeypad()Numeric keypad mode: keypad keys send plain digits, the same bytes as the top-row number keys.

Character set selection (G0 / G1)

WordDescription
SetUkG0()Select the UK character set as G0.
SetUkG1()Select the UK character set as G1.
SetUsG0()Select the US (ASCII) character set as G0.
SetUsG1()Select the US (ASCII) character set as G1.
SetSpecialG0()Select the special graphics / line-drawing set as G0.
SetSpecialG1()Select the special graphics / line-drawing set as G1.
SetAltRomG0()Select the alternate ROM character set as G0.
SetAltRomG1()Select the alternate ROM character set as G1.
SetAltRomSpecialG0()Select the alternate ROM special-graphics set as G0.
SetAltRomSpecialG1()Select the alternate ROM special-graphics set as G1.
SetShift2()Single-shift 2 (ESC N): use G2 for the next character only.
SetShift3()Single-shift 3 (ESC O): use G3 for the next character only.

Status and identification requests

WordDescription
RequestStatus()Ask the terminal to report its status (ESC [ 5 n).
RequestCursorPos()Ask the terminal to report where the cursor is (ESC [ 6 n).
RequestTerminalType()Ask the terminal to identify what it is (ESC [ 0 c).
ResetTerminal()Reset the terminal to its power-on state (ESC c).

Confidence tests and keyboard LEDs

WordDescription
ScreenAlignmentTest()Fill the screen with a test pattern of Es for checking alignment.
TestPowerUp()Invoke the power-up self test.
TestLoopback()Invoke the data-loopback test.
TestPowerUpRepeat()Repeat the power-up self test continuously.
TestLoopbackRepeat()Repeat the loopback test continuously.
LedsOff()Turn all four keyboard LEDs off.
Led1On()Turn keyboard LED 1 on.
Led2On()Turn keyboard LED 2 on.
Led3On()Turn keyboard LED 3 on.
Led4On()Turn keyboard LED 4 on.

VT52 compatibility mode

These only mean anything once the terminal has been switched into VT52 mode with SetVt52Mode; there it speaks only these short ESC-prefixed codes and no CSI at all. Several share their bytes with ANSI-mode words above — a real quirk of the protocol.

WordDescription
Vt52SetGraphics()Enter VT52 graphics mode (ESC F).
Vt52ResetGraphics()Leave VT52 graphics mode (ESC G).
Vt52CursorUp()Move the cursor up one line.
Vt52CursorDown()Move the cursor down one line.
Vt52CursorRight()Move the cursor right one column.
Vt52CursorLeft()Move the cursor left one column (ESC D — same bytes as IndexDown).
Vt52CursorHome()Jump to the home position (ESC H — same bytes as TabSet).
Vt52CursorPos(row, col)Direct cursor addressing: sends ESC Y then each 0-based coordinate offset by +32. Reconstructed from the real VT52 spec, so row 0, column 0 addresses as ESC Y space space — verify against your terminal.
Vt52ReverseIndex()Move up one line, scrolling if needed (ESC I).
Vt52ClearEol()Clear to the end of the line.
Vt52ClearEos()Clear to the end of the screen.
Vt52Ident()Ask the terminal to identify itself (ESC Z).
Vt52IdentResponse()The reply a VT52 sends to an identify request (ESC / Z).

Reading keystrokes (terminal → host)

WordDescription
Vt100KeyThe sum type EvalKey returns: KeyUp, KeyDown, KeyLeft, KeyRight, KeyPF1KeyPF4 (the function keys), KeyPad(Ch) (a keypad key, carrying its character), KeyEnter, and KeyUnknown(Raw) (anything unrecognised, carrying the original string).
EvalKey(raw)Classify one raw keystroke sequence into a Vt100Key. Recognises both the normal and application-mode forms of each arrow and keypad key, so you needn't track the terminal's mode. Anything it doesn't know comes back as KeyUnknown(raw). Pair with the InKey builtin — terminal's, not this machine's — to poll the live keyboard.

This machine documents no builtins of its own. It used to be the only page mentioning InKey; that word and the rest of the raw console family are now documented on terminal's page, and every word below is a Def declared here.

Who Uses It

UserHow
pac-vt100Both directions of the protocol — EvalKey decodes arrow escapes in the pure core; cursor addressing and clearing paint the shell.
weather-glassThe banding: day rows plain, night rows dim, marker rows bold, the header in reverse video.

The Machines It Uses

None — every escape sequence is built on the Chr builtin alone, standalone by design.