C0 in sequences Controls Inside Sequences
What a control character does when it arrives in the middle of a control sequence or a string.
CSI P… BS P… F
- DEC STD 070
- §3.5.1.1 Precedence of Control Functions, p. 3-18
- xterm
- Definitions
A control sequence is not always unbroken. A program can be interrupted, or a sequence can be written in two pieces with something else in between, and the terminal then meets a control character part-way through one.
In a control sequence a C0 control is carried out at once, and the sequence then carries on as if it had not been there: CSI 2 BS C moves back one column and then forward two. DEC STD 070 asks for this: control characters take precedence over the sequences around them (§3.5.1.1, p. 3-18). xterm’s parse tables do it, giving each C0 control its usual action in every state of a control sequence (csi_table). DEL, 7F, is ignored. The exceptions are CAN and SUB, which abandon the sequence, and ESC, which abandons it and starts another.
In a string, the text of an OSC, DCS, SOS, PM or APC, xterm acts on no C0 control except the ones that end or abandon it (sos_table), but it keeps the rest in the string, DEL and NUL among them (doparsing). What becomes of them depends on what the string is for. In a title, xterm turns each control into ?, except LF and tab, which it keeps, and DEL into ? too (ChangeGroup, NonLatin1), and a NUL ends the title there. So OSC 2 ; a BS b LF c NUL d ST sets the title a?b, a line feed, and c. BEL ends an OSC, as xterm has always allowed, and rings the bell inside a DCS. An ESC ends the string only as the first half of ST, ESC \. Followed by anything else, it abandons the string and starts an escape sequence, and the string is thrown away: xterm keeps collecting it only while it waits for the byte after the ESC, and carries out an OSC only on its terminator (doparsing).
libghostty-vt leaves the C0 controls out of a string instead, and keeps a DEL as it is, so a title comes out differently from xterm’s. And an ESC that is not part of ST ends an OSC and carries it out before starting the escape sequence, so a title cut short that way is still set. Those cases are known differences.
Support
| Terminal | Version | Support | Notes |
|---|---|---|---|
| VT220 | manual, 2nd ed., 1984 | Partly | §4.2 Control Characters: CAN, SUB and ESC cancel a sequence and DEL is ignored; other controls inside one are not addressed |
| VT510 | manual, 1st ed., 1993 | Partly | §4.3.5 and Table 4–1: ESC, CAN and SUB cancel a sequence; other controls inside one are not addressed |
| xterm | patch 412, xterm-411a | Yes | keeps controls in a string; in a title they become ?, but for LF and tab |
| libghostty-vt | 0f171f6 | Yes | CTL-5, CTL-6: leaves C0 controls out of a title and keeps DEL; CTL-7: an ESC that is not ST still carries out the OSC |
| kitty | 0.49.1, run in a VM | Partly | fails CTL-4 of the 4 cases that show it working |
| foot | 1.28.0, run in a VM | Yes | passes the 4 cases that show it working |
| pyte | 0.8.2, run as a library | Partly | fails CTL-4 of the 4 cases that show it working |
| xterm.js headless | 6.0.0, run as a library | Yes | passes the 4 cases that show it working |
| alacritty_terminal | 0.26.0, run as a library | Yes | passes the 4 cases that show it working |
| rxvt-unicode | 9.31, run in a VM | Partly | fails CTL-4 of the 4 cases that show it working |
| VTE | 0.84.1, run as a library | Yes | passes the 4 cases that show it working |
Validation
CTL-1: Backspace inside a control sequence
ABCD
\e[2\bC # BS, then CUF 2
X
|ABCD_X__|
cursor 1,7
CTL-2: Carriage return
ABCD
\e[\r2C # CR, then CUF 2
X
|ABXD____|
cursor 1,4
CTL-3: Line feed, between the parameters
AB
\e[\n2C
X
|AB______|
|____X___|
cursor 2,6
CTL-4: DEL is ignored
AB
\e[1\x7f;5H
X
|AB__X___|
cursor 1,6
CTL-5: Controls in a title become ?, but for LF, and NUL ends it
\e]2;a\bb\nc\x00d\x1fe\a
|________|
title "a?b\nc"
CTL-6: DEL becomes ? too
\e]2;a\x7fb\a
|________|
title "a?b"
CTL-7: An ESC abandons a string
\e]2;cut\e7 # ESC 7, DECSC, not ST
X
|X_______|
title ""