# Single Shift Two and Three (SS2, SS3)

> Print the next character from the G2 or G3 set.

- **Sequence:** `ESC N`
- **ECMA-48:** [§8.3.141 SS2 – Single-Shift Two, p. 70](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n83/mode/1up)
- **DEC STD 070:** [§3.8.3.2 Single Shift Two, p. 3-80](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n184/mode/1up)
- **VT220:** [§4.4.4 Invoking Character Sets with Single Shifts](https://vt100.net/docs/vt220-rm/chapter4.html#S4.4.4)
- **VT510:** [4.2 Control Characters](https://vt100.net/docs/vt510-rm/chapter4.html#S4.2)

SS2 prints the next character from the G2 set, and SS3 (`ESC O`) from the G3
set, without changing the set the characters after it come from. The VT510
manual puts it as temporarily mapping the set into GL "for the next graphic
character". G2 and G3 are filled with `ESC * F` and `ESC + F`; see
[SO and SI](https://control-codes.page/c0/so/index.html.md) for designating and invoking sets.

SS3 is
[ECMA-48 §8.3.142, p. 70](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n83/mode/1up)
and [DEC STD 070 §3.8.3.2, p. 3-81](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n185/mode/1up).

DEC STD 070 says what may come between the single shift and its character.
If the next character is not a graphic character, the single shift "is
ignored and the received character is processed as if the single shift had
not been received". The exception is one SO or SI, which is carried out but
"does not cancel the Single Shift". libghostty-vt instead keeps a single
shift until the next character it prints: `print` in `Terminal.zig` clears
it, and no control character does. A combining character counts, and uses
the single shift up. xterm likewise clears its single shift only when
it writes text (`WriteNow` in
[`charproc.c`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L1703-L1715)).

SS2 and SS3 are C1 controls. ECMA-48 defines them both as the single bytes
`8E` and `8F` and as `ESC N` and `ESC O`. libghostty-vt decodes its input as
UTF-8, so it accepts only the `ESC` forms.

## Support

| Terminal | Version | Support | Notes |
| --- | --- | --- | --- |
| VT220 | [manual](https://vt100.net/docs/vt220-rm/), 2nd ed., 1984 | Yes | [§4.4.4 Invoking Character Sets with Single Shifts](https://vt100.net/docs/vt220-rm/chapter4.html#S4.4.4) |
| VT510 | [manual](https://vt100.net/docs/vt510-rm/), 1st ed., 1993 | Yes | [Table 4–2](https://vt100.net/docs/vt510-rm/chapter4.html#T4-2) |
| xterm | patch 412, [xterm-411a](https://github.com/ThomasDickey/xterm-snapshots/tree/xterm-411a) | Yes | the single shift lasts until text is written |
| libghostty-vt | [0f171f6](https://github.com/ghostty-org/ghostty/tree/0f171f650a1cbbd6df4a2573b6b0c7de57cc6d90) | Yes | the single shift lasts until the next printed character |
| kitty | 0.49.1, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/kitty.json) | No | fails the 3 cases that show it working |
| foot | 1.28.0, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/foot.json) | Partly | fails SS2-3 of the 3 cases that show it working |
| pyte | 0.8.2, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/pyte.json) | No | fails the 3 cases that show it working |
| xterm.js headless | 6.0.0, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/xtermjs.json) | No | fails the 3 cases that show it working |
| alacritty_terminal | 0.26.0, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/alacritty.json) | No | fails the 3 cases that show it working |
| ConEmu | build 230724, its [documentation](https://conemu.github.io/en/AnsiEscapeCodes.html) | No | not in ConEmu's [ANSI escape codes](https://conemu.github.io/en/AnsiEscapeCodes.html) |
| rxvt-unicode | 9.31, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/urxvt.json) | No | fails the 3 cases that show it working |
| VTE | 0.84.1, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/vte.json) | No | fails the 3 cases that show it working |

## Validation

### SS2-1: One character from G2

Input, one step per line:

```text
\e*0      # G2 is DEC Special Graphics
\eN       # SS2
qq
```

Expected screen:

```text
|─q________|
```

### SS2-2: One character from G3

Input, one step per line:

```text
\e+0      # G3 is DEC Special Graphics
\eO       # SS3
qq
```

Expected screen:

```text
|─q________|
```

### SS2-3: An SO in between does not cancel it

Input, one step per line:

```text
\e*0      # G2 is DEC Special Graphics
\e)B      # G1 is ASCII
\eN       # SS2
\x0e      # SO, which may come between
q
```

Expected screen:

```text
|─_________|
```

---

This is the Markdown version of <https://control-codes.page/esc/ss2/>. On that page every validation case runs live in libghostty-vt, the terminal emulation core of Ghostty, compiled to WebAssembly.

The validation cases are written in the notation described in <https://control-codes.page/notation/index.html.md>.
