# Set Top and Bottom Margins (DECSTBM)

> Set the lines of the scrolling region.

- **Sequence:** `CSI Pt ; Pb r`
- **Defaults:** Pt = 1, Pb = the last line
- **DEC STD 070:** [§5.4.3 Set Top and Bottom Margins, p. 5-25](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n279/mode/1up)
- **VT510:** [DECSTBM—Set Top and Bottom Margins](https://vt100.net/docs/vt510-rm/DECSTBM.html)

Set the top margin to line `Pt` and the bottom margin to line `Pb`. The
lines between them, both included, are the *scrolling region*: a
[line feed](https://control-codes.page/c0/lf/index.html.md) on the bottom margin scrolls only those lines, as
[RI](https://control-codes.page/esc/ri/index.html.md) on the top margin does the other way, and [IL](https://control-codes.page/csi/il/index.html.md),
[DL](https://control-codes.page/csi/dl/index.html.md), [SU](https://control-codes.page/csi/su/index.html.md) and [SD](https://control-codes.page/csi/sd/index.html.md) act only inside it. A
parameter left out, or `0`, takes its default, so `CSI r` puts the margins
back at the first and last lines of the screen.

The top margin has to be above the bottom one. DEC STD 070 says that if
`Pt` is equal to or greater than `Pb`, the control "will be ignored (not
executed)": nothing changes, and the cursor does not move. A region is at
least two lines.

It also says a bottom margin past the last line is ignored the same way.
xterm instead takes it as the last line
([`CASE_DECSTBM`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L4773-L4790)), and esctest2's
`test_DECSTBM_MaxSizeOfRegionIsPageSize` expects that. libghostty-vt does as
xterm does.

Setting the margins moves the cursor to the home position: the first
column of the first line, or with [origin mode](https://control-codes.page/modes/decom/index.html.md) on, the top
and left margins. The left and right margins are set by
[DECSLRM](https://control-codes.page/csi/decslrm/index.html.md).

## Validation

### DECSTBM-1: Homes the cursor

Input, one step per line:

```text
\e[3;2H
\e[2;3r
```

Expected screen:

```text
|_____|
|_____|
|_____|
|_____|
cursor 1,1
```

### DECSTBM-2: A one-line region is ignored

Input, one step per line:

```text
1\r\n2\r\n3\r\n4
\e[3;3r   # top equal to bottom: not executed
\n        # so the whole screen scrolls
```

Expected screen:

```text
|2____|
|3____|
|4____|
|_____|
cursor 4,2
```

### DECSTBM-3: A bottom margin past the screen

Input, one step per line:

```text
\e[1;2H
x
\e[2;9r   # the bottom is taken as line 4
\e[4;1H
\n        # scrolls lines 2 to 4 only
```

Expected screen:

```text
|_x___|
|_____|
|_____|
|_____|
cursor 4,1
```

### DECSTBM-4: Left out, the margins are the screen's

Input, one step per line:

```text
1\r\n2\r\n3\r\n4
\e[2;3r
\e[r      # back to lines 1 to 4
\e[4;1H
\n
```

Expected screen:

```text
|2____|
|3____|
|4____|
|_____|
```

### DECSTBM-5: Homes to the margin in origin mode

Input, one step per line:

```text
\e[?6h    # origin mode
\e[2;3r
```

Expected screen:

```text
|_____|
|_____|
|_____|
|_____|
cursor 2,1
```

---

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