^[control-codes live in libghostty-vt

Escape sequences

CSI Control Sequence Introducer

Begin a control sequence: parameters, intermediates and a final byte.

CSI Pm Pi Pf
ECMA-48
§8.3.16 CSI – Control Sequence Introducer, p. 36
DEC STD 070
§3.5.3 Control Sequences, p. 3-23
VT510
4.3.3 Control Sequences

CSI does nothing by itself. It starts a control sequence, and most of the sequences on this site are control sequences. ECMA-48 gives the format in §5.4, p. 10:

PartBytesWhat it is
CSIESC [the introducer
P…P0–9 : ; < = > ?parameter bytes, if any
I…Ispace and ! to /intermediate bytes, if any
F@ to ~the final byte, which ends the sequence

The intermediate and final bytes together name the function: CSI 2 J is ED, and CSI 2 SP q, with a space as intermediate, is something else entirely. Final bytes p to ~ are set aside for private use, which is where many of DEC’s sequences live.

Parameters. Numbers are written in decimal and separated by ;, as ECMA-48 sets out in §5.4.2, p. 12. An empty parameter stands for the function’s default: CSI ; 5 H is line 1, column 5. Leading zeros do not count, and DEC STD 070 makes a parameter of zeros alone the default too, which is why a 0 so often means 1 (§3.5.3.1, p. 3-24). Trailing empty parameters may be left out, separators and all. A value too large to hold should be taken as the largest the terminal supports.

Private parameters. A parameter string that starts with <, =, > or ? is private: its meaning is not standardized, and CSI ? 6 h (DECOM) and CSI 6 h are different functions. DEC STD 070 says one of those bytes anywhere else makes the whole sequence invalid, to be ignored up to and including its final byte (§3.5.3.4, p. 3-26). So does a parameter byte after an intermediate (§3.5.3, p. 3-23), and so does :, which DEC STD 070 reserves (§3.5.3.1, p. 3-24). SGR is the exception everyone makes now, with colons separating the parts of a color or underline style.

Ending early. A sequence ends at its final byte. CAN or SUB part-way through abandons it, and an ESC abandons it and starts another; other control characters inside one are carried out on the spot, as BEL’s page shows. A sequence the terminal does not implement is ignored as if it had not arrived.

The 8-bit form. ECMA-48 also gives CSI as the single byte 9B. A terminal reading UTF-8 does not take it as CSI: the code point U+009B is ignored, and a lone 9B byte is not valid UTF-8. A default xterm ignores that byte as well when printable text follows it, while libghostty-vt prints it as U+FFFD, so that case is a known difference. Either way, what follows is printed as text. The UTF-8 page has the details.

Validation

CSI-1: Empty and zero parameters take the default

\e[;3H    # line left out
A
\e[000;002H
B
\e[3;H    # column left out
C
|_BA___|
|______|
|C_____|

CSI-2: A value too large is taken as large as possible

\e[99999999;2H
X
|______|
|______|
|_X____|

CSI-3: Malformed sequences are ignored

\e[2?3H   # private marker in the middle
\e[2\s3H  # parameter after an intermediate
\e[2:3H   # a colon
X
|X_____|
|______|
|______|

CSI-4: An unknown sequence is ignored

AB
\e[5~     # a function key code, not a control function here
C
|ABC___|
cursor 1,4

CSI-5: The 8-bit forms

\x9b      # a lone byte: xterm ignores it before text
2;2H
|2;2H__|
|______|

CSI-6: U+009B is not CSI either

\u{9b}    # U+009B as UTF-8: ignored
2;2H
|2;2H__|
|______|
Every example on these pages runs in your browser, in libghostty-vt compiled to WebAssembly.