# Tabulation Stop Mode (TSM)

> Whether a tab stop applies to every line or only one.

- **Sequence:** `CSI 18 h`
- **ECMA-48:** [§7.2.17 TSM – Tabulation Stop Mode, p. 25](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n38/mode/1up)
- **DEC STD 070:** [§5.10.1.3 Modes That Cannot Be Changed, p. 5-134](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n388/mode/1up)

Mode 18 decides whether setting or clearing a tab stop, with
[HTS](https://control-codes.page/esc/hts/index.html.md), [TBC](https://control-codes.page/csi/tbc/index.html.md) or [CTC](https://control-codes.page/csi/ctc/index.html.md), does so in that
column of every line, or only in the line the cursor is on.

| State | ECMA-48 calls it | Meaning |
| --- | --- | --- |
| Reset, `CSI 18 l` | MULTIPLE | in every line |
| Set, `CSI 18 h` | SINGLE | in the active line only |

[DEC STD 070 §5.10.1.3, p. 5-134](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n388/mode/1up) lists it among the X3.64 modes a DEC terminal may treat as fixed,
as reset. The VT510 manual's [table of modes for DECRPM](https://vt100.net/docs/vt510-rm/DECRPM.html) marks it as always reset. In xterm, as on a DEC terminal, a tab stop belongs to a column, the same on every line.

xterm ignores `CSI 18 h` and `CSI 18 l` ([`ansi_modes`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L7399-L7424) handles only modes 2,
4, 12 and 20), and [DECRQM](https://control-codes.page/csi/decrqm/index.html.md) reports the mode as permanently
reset, `4` ([`do_ansi_rqm`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/misc.c#L5408-L5463)). libghostty-vt ignores it too, but does not recognize the
number, so it answers DECRQM with `0`. Both DECRQM cases are known
differences.

## Validation

### TSM-1: Reported as permanently reset

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

Input, one step per line:

```text
\e[18$p
```

Expected screen:

```text
|_____|
reply \e[18;4$y
```

### TSM-2: Even after it is set

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

Input, one step per line:

```text
\e[18h
\e[18$p
```

Expected screen:

```text
|_____|
reply \e[18;4$y
```

### TSM-3: A tab stop still applies to every line

Input, one step per line:

```text
\e[3g       # clear every tab stop
\e[18h     # ignored
\e[1;4H
\eH         # set a stop in line 1
\e[2;1H
\t
X
```

Expected screen:

```text
|______|
|___X__|
cursor 2,5
```

---

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