^[control-codes live in libghostty-vt

Control sequences

CUB Cursor Backward

Move the cursor left n columns.

CSI Pn D
Defaults
Pn = 1
ECMA-48
§8.3.18 CUB – Cursor Left, p. 36
DEC STD 070
§5.4.4 Cursor Backward, p. 5-47
VT510
CUB—Cursor Backward

Move the cursor Pn columns to the left. A 0 is taken as 1. The cursor stops at the left margin if it started at or right of it, and at the first column otherwise. It never moves to another line.

If a wrap is pending, the wrap is cancelled, as DEC STD 070 asks (§D.6.1, p. D-13) and xterm’s CursorBack does, and the cursor moves from the last column, so CSI D after filling a line leaves the cursor on the second-last column. (With xterm’s reverse wraparound mode on, the pending wrap instead uses up one of the Pn columns; that mode is off by default.)

The other three directions are CUU (A, up), CUD (B, down) and CUF (C, right), and they work the same way.

Validation

CUB-1: Left n columns

ABCD
\e[3D
X
|AXCD_|

CUB-2: Stops at the first column

ABC
\e[99D
X
|XBC__|

CUB-3: From a pending wrap

ABCDE     # fills the line; a wrap is pending
\e[D
X
|ABCXE|
|_____|

CUB-4: Stops at the left margin

\e[?69h   # allow left and right margins
\e[2;4s   # margins at columns 2 and 4
\e[1;4H
\e[9D
X
|_X___|

libghostty-vt stops at the first column rather than the left margin. With reverse wraparound off, which is the default, it clamps the move to column 1 and does not look at the margins; only its reverse-wraparound path does. DEC STD 070 is explicit that the cursor “will not move beyond the Left Margin” when it starts at or inside it, xterm stops there, and esctest2’s test_CUB_StopsAtLeftMarginInScrollRegion checks for it.

Every example on these pages runs in your browser, in libghostty-vt compiled to WebAssembly.