# Cursor Tabulation Control (CTC)

> Set or clear tab stops: at the cursor, or all of them.

- **Sequence:** `CSI Pm W`
- **Defaults:** Ps = 0
- **ECMA-48:** [§8.3.17 CTC – Cursor Tabulation Control, p. 36](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n49/mode/1up)

Set or clear tab stops, chosen by each parameter:

| `Ps` | ECMA-48 | libghostty-vt |
| --- | --- | --- |
| `0`, or left out | set a tab stop at the cursor's column | the same, as [HTS](https://control-codes.page/esc/hts/index.html.md) does |
| `2` | clear the tab stop at the cursor's column | the same, as `CSI 0 g` ([TBC](https://control-codes.page/csi/tbc/index.html.md)) does |
| `5` | clear every tab stop | the same, as `CSI 3 g` does |
| `1`, `3`, `6` | set or clear *line* tabulation stops | ignored |
| `4` | clear the tab stops in the cursor's line | ignored |

Line tabulation stops are for [vertical tabs](https://control-codes.page/c0/vt/index.html.md), which terminals
treat as line feeds, so terminals have no use for them. Neither DEC STD 070
nor the VT510 manual defines CTC.

xterm does not implement CTC at all: its parse table sends `CSI … W` straight
back to the ground state
([`VTPrsTbl.c`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/VTPrsTbl.c#L584-L588)), so every value is ignored. The
cases follow xterm, as the [sources](https://control-codes.page/sources/index.html.md) page explains, and expect the
tab stops to be left alone. libghostty-vt does implement the character tab
stop values `0`, `2` and `5`, which makes CTC-1 to CTC-3 known differences.

`CSI ? 5 W` is a different function, DEC's
[DECST8C](https://vt100.net/docs/vt510-rm/DECST8C.html): it puts the tab
stops back to one every eight columns, starting at column 9, the way they
start out. xterm runs it only when emulating a VT510 or later
([`CASE_DECST8C`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L5890-L5897) checks `vtXX_level >= 5`),
and a default xterm is a VT420, so there it is ignored too. libghostty-vt
always runs it, which makes CTC-4 a known difference. Many cases on this
site begin with `CSI ? 5 W` before touching any tab stop; since the tab stops
start out every eight columns anyway, those cases come out the same whether
it runs or not.

## Validation

### CTC-1: Setting a tab stop is ignored

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[3g     # clear every tab stop
\e[1;4H
\e[W      # would set one at column 4
\r
A\tX      # no tab stop, so the tab goes to the last column
```

Expected screen:

```text
|A__________X|
```

### CTC-2: Clearing the tab stop at the cursor is ignored

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?5W    # tab stops every 8 columns
\e[1;9H
\e[2W     # would clear the one at column 9
\r
\tX       # the tab stop at column 9 is still there
```

Expected screen:

```text
|________X___|
```

### CTC-3: Clearing every tab stop is ignored

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?5W    # tab stops every 8 columns
\e[5W     # would clear them all
\tX
```

Expected screen:

```text
|________X___|
```

### CTC-4: DECST8C is ignored by a VT420

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[3g     # clear every tab stop
\e[?5W    # a VT510 would restore them; a VT420 ignores it
\tX       # no tab stop, so the tab goes to the last column
```

Expected screen:

```text
|___________X|
```

---

This is the Markdown version of <https://control-codes.page/csi/ctc/>. 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>.
