# Column Mode (DECCOLM)

> Switch the screen between 80 and 132 columns, clearing it.

- **Sequence:** `CSI ? 3 h/l`
- **Defaults:** reset, 80 columns
- **?40**, Allow 80/132 Column Mode: `CSI ? 40 h/l`
- **DEC STD 070:** [§5.4.6 Set/Reset Column Mode, p. 5-71](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n325/mode/1up)
- **VT220:** [§4.6.10 Column Mode (DECCOLM)](https://vt100.net/docs/vt220-rm/chapter4.html#S4.6.10)
- **VT510:** [DECCOLM—Select 80 or 132 Columns per Page](https://vt100.net/docs/vt510-rm/DECCOLM.html)

`CSI ? 3 h` makes the screen 132 columns wide, and `CSI ? 3 l` makes it 80.
Either way, DEC STD 070 says it clears the screen, removes the scrolling
region, and moves the cursor to the top-left, "even if the terminal was
already in the selected state". Every line goes back to
[single-width](https://control-codes.page/esc/decdwl/index.html.md) too. DEC STD 070 makes column mode an optional
extension at every level, marked `1X` to `4X`.

xterm decides what to do in three steps, in this order
([`srm_DECCOLM`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L7559-L7580),
[`set_column_mode`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L7462-L7471)):

```mermaid
flowchart TD
    start(["CSI ? 3 h or l"]) --> m40{"Mode 40?"}
    m40 -- reset --> ignored(["Ignored"])
    m40 -- set --> ncsm{"DECNCSM?"}
    ncsm -- reset --> clear["Clear the screen"]
    ncsm -- set --> resize
    clear --> resize{"ColumnMode?"}
    resize -- "allowed, and the width changes" --> ask["Ask for 80 or 132 columns"]
    resize -- otherwise --> rest
    ask --> rest(["Set or reset column mode<br>Lines back to single width<br>Margins removed<br>Cursor home"])
```

- **Mode 40**, xterm's *allow 80/132 column mode*, is off by default: the
  `c132` resource is
  [`False`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L432).
  Without it DECCOLM is ignored.
- **Mode 95**, [DECNCSM](https://control-codes.page/modes/decncsm/index.html.md), keeps a VT510 from clearing the
  screen. xterm honors it only at VT510 level or above, and a default xterm
  is a VT420, so it always clears.
- **`ColumnMode`** is a window operation, a switch apart from mode 40. Put
  in the `disallowedWindowOps` resource, it keeps the width as it was while
  the rest of DECCOLM still happens, DECRQM included. A default xterm
  allows it
  ([`DEF_DISALLOWED_WINDOW`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/main.h#L170)),
  so the cases expect the resize unless they say otherwise. It suits a host
  that owns the terminal's size, such as a multiplexer laying out panes.

The case runner draws only as many columns as each expected
screen has, but a cursor sent to column 999 stops at the real last column,
which shows the width. [DECRQM](https://control-codes.page/csi/decrqm/index.html.md) reports both modes.

libghostty-vt implements DECCOLM and mode 40 as xterm does, but has no
setting like `ColumnMode`: with mode 40 set it always resizes, so
DECCOLM-8, which keeps the width, is a known difference. It does clear the
screen and home the cursor as xterm does, so DECCOLM-7 passes.

## Support

| Terminal | Version | Support | Notes |
| --- | --- | --- | --- |
| VT220 | [manual](https://vt100.net/docs/vt220-rm/), 2nd ed., 1984 | Yes | [§4.6.10 Column Mode (DECCOLM)](https://vt100.net/docs/vt220-rm/chapter4.html#S4.6.10) |
| VT510 | [manual](https://vt100.net/docs/vt510-rm/), 1st ed., 1993 | Yes | [DECCOLM](https://vt100.net/docs/vt510-rm/DECCOLM.html) |
| xterm | patch 412, [xterm-411a](https://github.com/ThomasDickey/xterm-snapshots/tree/xterm-411a) | Partly | ignored unless mode 40 allows it; off by default (`c132`) |
| libghostty-vt | [0f171f6](https://github.com/ghostty-org/ghostty/tree/0f171f650a1cbbd6df4a2573b6b0c7de57cc6d90) | Yes | with mode 40, as xterm; no setting like xterm's `ColumnMode` to keep the width (DECCOLM-8) |
| kitty | 0.49.1, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/kitty.json) | Partly | fails DECCOLM-4, DECCOLM-5, DECCOLM-6 of the 4 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) | No | fails the 4 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) | Partly | fails DECCOLM-5 of 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 4 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) | Partly | fails DECCOLM-4, DECCOLM-5 of the 4 cases that show it working |
| ConEmu | build 230724, its [documentation](https://conemu.github.io/en/AnsiEscapeCodes.html) | No | not in ConEmu's list, [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) | Partly | fails DECCOLM-3, DECCOLM-6 of the 4 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) | Partly | fails DECCOLM-4, DECCOLM-5 of the 4 cases that show it working |

## Validation

### DECCOLM-1: Ignored unless allowed

Input, one step per line:

```text
ABC
\e[2;3H
\e[?3h    # 132 columns, not allowed
\e[?3$p   # ask about column mode
```

Expected screen:

```text
|ABC___|
|______|
cursor 2,3
reply \e[?3;2$y
```

### DECCOLM-2: Mode 40 is off by default

Input, one step per line:

```text
\e[?40$p
\e[?40h
\e[?40$p
```

Expected screen:

```text
|______|
reply \e[?40;2$y\e[?40;1$y
```

### DECCOLM-3: 132 columns, cleared, cursor home

Input, one step per line:

```text
\e[?40h   # allow 80/132
ABC
\e[2;3H
\e[?3h    # 132 columns
```

Expected screen:

```text
|______|
|______|
cursor 1,1
```

### DECCOLM-4: The new width

Input, one step per line:

```text
\e[?40h   # allow 80/132
\e[?3h    # 132 columns
\e[1;999H
\e[?3$p
```

Expected screen:

```text
|______|
cursor 1,132
reply \e[?3;1$y
```

### DECCOLM-5: Back to 80

Input, one step per line:

```text
\e[?40h   # allow 80/132
\e[?3h    # 132 columns
\e[?3l    # 80 columns
\e[1;999H
```

Expected screen:

```text
|______|
cursor 1,80
```

### DECCOLM-6: The margins are removed

Input, one step per line:

```text
\e[?40h   # allow 80/132
\e[1;2r   # region is lines 1 and 2
\e[?69h   # allow left and right margins
\e[1;2s   # margins at columns 1 and 2
\e[?3h    # 132 columns
\r\nHello # would wrap at column 2 and stay in lines 1 and 2
\r\nWorld
```

Expected screen:

```text
|______|
|Hello_|
|World_|
|______|
```

### DECCOLM-7: Cleared and homed without a resize

*Run with DECCOLM kept from resizing, as xterm is once ColumnMode is disallowed.*

Input, one step per line:

```text
\e[?40h   # allow 80/132
ABC
\e[2;3H
\e[?3h    # 132 columns, but the width is kept
```

Expected screen:

```text
|______|
|______|
cursor 1,1
```

### DECCOLM-8: The width is kept, the mode is set

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

*Run with DECCOLM kept from resizing, as xterm is once ColumnMode is disallowed.*

Input, one step per line:

```text
\e[?40h   # allow 80/132
\e[?3h    # 132 columns, but the width is kept
\e[1;999H
\e[?3$p
```

Expected screen:

```text
|______|
cursor 1,6
reply \e[?3;1$y
```

---

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