^[control-codes live in libghostty-vt

Control sequences

XTPUSHSGR, XTPOPSGR Push and Pop Video Attributes

Save the current SGR attributes on a stack, or some of them, and bring them back later.

XTPUSHSGR Push Video Attributes

CSI Pm # {

XTPOPSGR Pop Video Attributes

CSI # }

XTPUSHSGR Push Video Attributes

CSI Pm # p

XTPOPSGR Pop Video Attributes

CSI # q
xterm
Functions using CSI

CSI # { saves the current SGR attributes, the ones the next character will be written with, and CSI # } brings them back. A program can change the colors and style for a while and return to whatever was set before, without having to know what that was. xterm keeps the saved attributes on a stack, so pushes and pops nest. CSI # p and CSI # q are the same two functions, there for languages in which a { in a string is awkward (csi_hash_table).

With no parameters, a push saves all of the attributes. With parameters, named by their SGR numbers, it saves only those, and the matching pop restores only those and leaves the rest as they are:

PmAttribute
1bold
2faint
3italic
4underline
5blink
7inverse
8invisible
9crossed out
21double underline
30the foreground color
31the background color

A color has many SGR numbers, so 30 and 31 stand for all of them; 10 and 11, which older xterms used for the colors, are still taken. Any other number is ignored. A pop with nothing pushed does nothing.

The stack is ten deep. xterm counts pushes past the tenth without saving anything for them, and the pops that match them restore nothing, so pushes and pops still pair up: after eleven pushes, the first pop restores nothing and the second restores what the tenth push saved (xtermPushSGR, xtermPopSGR). Nothing resets the stack: neither RIS nor DECSTR touches it, so what was pushed before a reset can be popped after it. These are xterm’s own, and in a default build (OPT_XTERM_SGR). xterm also has XTREPORTSGR, CSI Pt ; Pl ; Pb ; Pr # |, which reports the attributes that every cell in a rectangle shares.

Contour’s SGRSAVE and SGRRESTORE. Contour’s Save and Restore SGR attributes, read at commit d5541aa, gives CSI # { and CSI # } other names and a simpler meaning: one saved set of attributes rather than a stack, and no parameters. It says that pushing and popping some attributes and not others is functionality a terminal need not have and a program can manage for itself. In Contour at release 0.8.0, CSI # { saves all of the attributes over whatever was saved before, CSI # } restores them as often as it is sent, and a push with parameters is not recognized (Screen::saveGraphicsRendition, Functions.hpp). A program that pushes once and pops once gets the same from both; one that nests gets the innermost attributes back each time from Contour.

libghostty-vt recognizes neither function and ignores both, so the cases in which a pop changes anything are known differences.

Support

TerminalVersionSupportNotes
VT220manual, 2nd ed., 1984Nonot in the manual
VT510manual, 1st ed., 1993Nonot in the manual
xtermpatch 412, xterm-411aYes
libghostty-vt0f171f6NoXTPUSHSGR-1 to XTPUSHSGR-3, XTPUSHSGR-5 to XTPUSHSGR-7: ignores both
kitty0.49.2, its documentationNonot in its CSI dispatch; CSI # P and CSI # Q push and pop colors instead (vt-parser.c)
VTE0.84.1, sourceNorecognized and ignored (vteseq.cc)

Validation

XTPUSHSGR-1: Pop brings everything back

\e[1;31m
\e[#{
\e[0;4m
A
\e[#}
B
|AB__|
attr 1,1 underline -bold fg=default
attr 1,2 bold -underline fg=1

XTPUSHSGR-2: Only the attributes named

\e[1;31m
\e[1#{     # save bold only
\e[0m
A
\e[#}
B
|AB__|
attr 1,1 plain
attr 1,2 bold fg=default

XTPUSHSGR-3: Pushes nest

\e[1m
\e[#{
\e[3m
\e[#{
\e[0m
A
\e[#}
B
\e[#}
C
|ABC_|
attr 1,1 plain
attr 1,2 bold italic
attr 1,3 bold -italic

XTPUSHSGR-4: A pop with nothing pushed does nothing

\e[1m
\e[#}
A
|A___|
attr 1,1 bold

XTPUSHSGR-5: The other final bytes

\e[1m
\e[#p
\e[0m
A
\e[#q
B
|AB__|
attr 1,1 plain
attr 1,2 bold

XTPUSHSGR-6: Past ten, a push saves nothing

\e[1m
\e[#{\e[#{\e[#{\e[#{\e[#{\e[#{\e[#{\e[#{\e[#{\e[#{   # ten, all bold
\e[0;3m
\e[#{      # the eleventh is not saved
\e[0m
\e[#}      # so its pop restores nothing
A
\e[#}      # and the next brings back the tenth
B
|AB__|
attr 1,1 plain
attr 1,2 bold -italic

XTPUSHSGR-7: RIS leaves the stack alone

\e[1m
\e[#{
\ec
\e[#}
A
|A___|
attr 1,1 bold
Every example on these pages runs in your browser, in libghostty-vt compiled to WebAssembly.