Locking shifts
The locking shifts are escape sequences that invoke character sets G1, G2, or G3 into the GL (left) or GR (right) half of the code table. Unlike single shifts (SS2/SS3), locking shifts remain in effect until another shift is performed.
Syntax
ESC n LS2 — Locking-shift G2 into GL
ESC o LS3 — Locking-shift G3 into GL
ESC ~ LS1R — Locking-shift G1 into GR
ESC } LS2R — Locking-shift G2 into GR
ESC | LS3R — Locking-shift G3 into GR
LS2 = 0x1b, "n" ; LS3 = 0x1b, "o" ; LS1R = 0x1b, "~" ; LS2R = 0x1b, "}" ; LS3R = 0x1b, "|" ;
The GL shifts LS0 and LS1 are the 8-bit equivalents of SI and SO respectively — see SO/SI.
Description
Per ECMA-48 §8.3.75–§8.3.81, the locking shift functions are used for code extension purposes. Their detailed semantics are defined in ECMA-35.
In the VT100/VT220 character set architecture:
- GL (Graphic Left, 0x20-0x7F) and GR (Graphic Right, 0xA0-0xFF) are the two active code areas for graphic characters.
- Four character sets — G0, G1, G2, G3 — can be designated via escape sequences (e.g.,
ESC ( Bfor ASCII into G0). - The locking shifts control which of G0–G3 is invoked into GL or GR.
| Shift | Invokes | Into |
|---|---|---|
| LS0 / SI | G0 | GL |
| LS1 / SO | G1 | GL |
| LS2 | G2 | GL |
| LS3 | G3 | GL |
| LS1R | G1 | GR |
| LS2R | G2 | GR |
| LS3R | G3 | GR |
Practical relevance
Locking shifts beyond LS0/LS1 (SI/SO) are rarely used in modern terminals. UTF-8 has largely supplanted the ISO 2022 character set switching mechanism. Most modern terminal emulators support these sequences for compatibility but they see little practical use outside of legacy applications.
Specifications
| Specification | Section |
|---|---|
| ECMA-48 | §8.3.75–§8.3.81 |
Terminal support
| Terminal | Support | Version | Notes |
|---|---|---|---|
| Terminal Emulators | |||
| Alacritty | ✗ | No | No locking shift (LS2/LS3/LS1R/LS2R/LS3R) support found |
| Bobcat | ✓ | 0.9.0 | Locking shift sequences, part of TerminalCtrl character set handling |
| contour | ✗ | No | LS2, LS3, LS1R, LS2R, LS3R not found; only LS0/LS1 (SI/SO) supported |
| foot | ~ | 1.8.0 | LS2 and LS3 supported (vt.c:485-497), LS1R/LS2R/LS3R not implemented |
| Ghostty | ✓ | v1.0.0 | LS2, LS3 (ESC n, ESC o) and LS1R, LS2R, LS3R (ESC ~, ESC }, ESC |) handled in stream.zig:2269-2335 |
| iTerm2 | ✗ | No | LS2, LS3, LS1R, LS2R, LS3R not implemented |
| Kitty | ✗ | No | |
| Konsole | ~ | Yes | LS2/LS3 via ESC n/o; LS1R/LS2R/LS3R not found |
| mintty | ✓ | Yes | LS2 at src/termout.c:2034, LS3 at src/termout.c:2037, LS1R at src/termout.c:2040, LS2R at src/termout.c:2043, LS3R at src/termout.c:2046 |
| mlterm | ✓ | rel-0_2_1 | Delegated to mef library for ISO-2022 encodings |
| PuTTY | ~ | 0.45 | Only LS0/LS1 (SO/SI) supported; LS2, LS3, LS1R, LS2R, LS3R not implemented |
| Rio | ✗ | No | LS2/LS3/LS1R/LS2R/LS3R not implemented |
| rxvt-unicode | ~ | rxvt-unicode-1.2 | Only LS2/LS3 (ESC n/o); LS1R/LS2R/LS3R not supported |
| st | ~ | 0.6 | Only LS2/LS3 implemented, not LS1R/LS2R/LS3R |
| terminology | ✗ | No | Not implemented |
| VT100 | ✗ | No | |
| VTE | ~ | Yes | LS2, LS3, LS1R, LS2R, LS3R handlers exist but are mostly non-functional (code commented out) |
| WezTerm | ✗ | No | Only SO/SI (shift out/in) supported; no evidence of LS2, LS3, LS1R, LS2R, LS3R |
| Windows Terminal | ✓ | Yes | LS2, LS3 (ESC n/o) and LS1R, LS2R, LS3R (ESC ~/-/_) dispatch LockingShift/LockingShiftRight in OutputStateMachineEngine.cpp:241-254 |
| xterm | ✓ | Yes | Implemented in charproc.c:6167-6200. Requires ansi_level > 2 |
| xterm.js | ✓ | 3.5.0 | LS2, LS3, LS1R, LS2R, LS3R supported via setgLevel |
| Multiplexers | |||
| cy | ✗ | No | LS2/LS3/LS1R/LS2R/LS3R not found |
| GNU Screen | ✓ | v.4.2.0 | LS2/LS3/LS1R/LS2R/LS3R implemented in DoESC() at src/ansi.c:760-775 |
| tmux | ✗ | No | LS2/LS3/LS1R/LS2R/LS3R not implemented |
| tuios | ✓ | v0.0.15 | |
| Zellij | ✗ | No | |
See also
- SO/SI — Shift Out/In — G0/G1 locking shifts (the most commonly used pair)
- SS2/SS3 — Single Shifts — Temporary G2/G3 invocation for one character