# BiDi Modes (?1243, ?2500, ?2501)

> Swap the arrow keys in right-to-left text, mirror box drawing, and detect the direction of each paragraph, for a terminal that handles bidirectional text.

- **Sequence:** `CSI ? 1243 h/l`
- **?2500**, Box Drawing Mirroring: `CSI ? 2500 h/l`
- **?2501**, BiDi Autodetection: `CSI ? 2501 h/l`
- **Specification:** [BiDi in Terminal Emulators, v0.1, The escape sequences](https://gitlab.freedesktop.org/terminal-wg/bidi/-/blob/cc8686a93c8cc2f25a2f68f79c01b4822111509c/docs/30-recommendation/90-escape-sequences.md)

[*BiDi in Terminal Emulators*](https://terminal-wg.pages.freedesktop.org/bidi/)
is Egmont Koblinger's recommendation for how a terminal should show text
that runs right to left, such as Arabic and Hebrew, and text that mixes the
two directions. Version 0.1 was published in January 2019. Its
[escape sequences](https://terminal-wg.pages.freedesktop.org/bidi/recommendation/escape-sequences.html)
are two from ECMA-48, [BDSM](https://control-codes.page/ansi/bdsm/index.html.md) and [SCP](https://control-codes.page/csi/scp/index.html.md), and the
three private modes on this page, which ECMA-48 has nothing for. The
recommendation notes that their numbers are VTE's work in progress and not
meant to be final; VTE added them in 0.58, in 2019, and still uses them.

| Mode | Set | Reset | Default |
| --- | --- | --- | --- |
| `?1243` | the Left and Right arrow keys are swapped while the cursor is in a right-to-left paragraph | the arrow keys are never swapped | set |
| `?2500` | box drawing characters are mirrored in right-to-left text | they are drawn as they are | reset |
| `?2501` | each paragraph's direction is detected from its text, falling back on the one SCP set | the direction is the one SCP set | reset |

The numbers are VTE's. 2500 and 2501 borrow U+2500, where Unicode's box
drawing characters begin, which the recommendation calls "a total misuse
of the hexadecimal". 1243 is the permutation applied to the four arrow keys
taken in the order of the sequences they send, up, down, right and left:
the first two stay put and the last two change places.

BiDi is display only. The terminal keeps the text in the order it arrived,
and the cursor addresses cells in that order, so these modes change what
the user sees and what the arrow keys send, and nothing that DECRQCRA or a
cursor report can read back.

## The modes

**Arrow key swapping, `?1243`.** Text in a right-to-left paragraph runs
leftwards, so a user who presses Left means to move forwards through it,
while the program reads Left as backwards and moves the cursor the other
way on the screen. With the mode set,
the terminal sends Right's sequence for the Left key and Left's for the
Right key while the cursor is in a right-to-left paragraph, an autodetected
one included. It is the only one of the five settings that is global rather
than kept per paragraph, as every other mode that affects the keyboard is,
which is the recommendation's reason for making it a DEC private mode. It
is set by default, matching BDSM, so that a program that does nothing gets
keys that move the way they point.

**Box drawing mirroring, `?2500`.** Unicode already mirrors characters such
as brackets in right-to-left text. Set, this mode mirrors the box drawing
characters too, so that a frame drawn by a program that thinks left to
right still joins up when it is shown the other way round. mintty also
mirrors the Block Elements, U+2580 to U+259F.

**Autodetection, `?2501`.** In implicit mode, set, the terminal picks each
paragraph's direction from its first strong character, the way the Unicode
Bidirectional Algorithm does, and falls back on the direction SCP set when
there is none. The recommendation makes reset the default with some
hesitation: autodetection is not in ECMA-48, it gives fewer surprises to
someone who never uses right-to-left text, and it fixes many cases without
fixing all of them. In right-to-left mode, `cat /etc/services` comes out
mostly right with it, but with a lone `#` still on the wrong side. A
program that needs one or the other saves the mode, sets it, and restores
it on exit.

The recommendation has no sequence for the level of conformance, such as
whether a terminal remembers BiDi control characters; a terminal always uses
the highest level it implements.

## How the sequences apply

The recommendation marks this part as a draft. Arrow key swapping is global
and takes effect at once. The other four settings, BDSM, SCP, box drawing
mirroring and autodetection, are kept for each *paragraph*, the run of rows
from one hard newline to the next, and the terminal also has a *current*
value of each, the one the latest sequence set. A sequence on its own
changes nothing visible, any more than `CSI 31 m` on its own makes anything
red.

- A sequence always sets the current value. If the cursor is at the first
  position of a paragraph, the value is applied to that paragraph as well,
  even if it is already the current one, and the paragraph's other settings
  stay as they are. Anywhere else, the start of the paragraph's second row
  included, the paragraph keeps what it had.
- A newline that takes the cursor into a new paragraph gives that
  paragraph the current values. One that stays in the same paragraph does
  nothing.
- When text wraps and joins two paragraphs, the joined one keeps the first
  paragraph's settings.
- A new line scrolled in at the bottom, other than by a wrap, gets the
  current values.
- [ED](https://control-codes.page/csi/ed/index.html.md) makes each line it erases a paragraph of its own, with the
  current values: `ED 0` the lines below the cursor's, `ED 1` the lines
  above it, and `ED 2` every line, the scrollback excluded each time. The
  line between the scrollback and the screen becomes a hard newline too,
  even for an `ED 1` on the top row that erases nothing. This is how a
  full-screen program, which clears the screen with ED when it starts,
  gets its modes onto every line.
- [SU](https://control-codes.page/csi/su/index.html.md) and [SD](https://control-codes.page/csi/sd/index.html.md) make every boundary where lines are
  removed or inserted a hard newline, give each new blank line the current
  values as a paragraph of its own, and move each paragraph's settings with
  its text.
- When [DCH](https://control-codes.page/csi/dch/index.html.md), [EL](https://control-codes.page/csi/el/index.html.md) or the like splits a paragraph in
  two, both halves keep its settings.

[VTE](https://gitlab.gnome.org/GNOME/vte/-/blob/0.84.1/src/vte.cc#L3717-L3744)
applies a sequence to the paragraph when the cursor is in its first column
and the row before did not soft-wrap into it.

## Implementations

[VTE](https://gitlab.gnome.org/GNOME/vte/-/blob/0.84.1/src/modes.py#L1209-L1235)
implements all three, with the defaults above, and answers
[DECRQM](https://control-codes.page/csi/decrqm/index.html.md) for them with `1` or `2`. It honors arrow key
swapping only while BiDi is enabled in the widget.

mintty implements `?2500` and `?2501`, not `?1243`. Its autodetection is
set by default, where the recommendation and VTE leave it reset
([documentation](https://github.com/mintty/mintty/wiki/CtrlSeqs#bidirectional-rendering)).
It also has modes of its own to turn BiDi off, `?77096` for everything and
`?7796` for the current line.

A default xterm knows none of the three. It ignores them, and answers
DECRQM with `0`, not recognized, as it does for any mode it does not know
([`do_dec_rqm`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/misc.c#L5466)).
libghostty-vt does the same, and the cases follow xterm.

## Support

| Terminal | Version | Support | Notes |
| --- | --- | --- | --- |
| VT220 | [manual](https://vt100.net/docs/vt220-rm/), 2nd ed., 1984 | No | not in the manual |
| VT510 | [manual](https://vt100.net/docs/vt510-rm/), 1st ed., 1993 | No | not in the manual |
| xterm | patch 412, [xterm-411a](https://github.com/ThomasDickey/xterm-snapshots/tree/xterm-411a) | No | not recognized; DECRQM reports `0` |
| libghostty-vt | [919dca5](https://github.com/ghostty-org/ghostty/tree/919dca5c848d43a489a4cb2e1feade016f0fa2db) | No | not recognized; DECRQM reports `0` |
| ConEmu | build 230724, its [documentation](https://conemu.github.io/en/AnsiEscapeCodes.html) | No | not in its [ANSI escape codes](https://conemu.github.io/en/AnsiEscapeCodes.html) |
| mintty | 3.8.3, its [documentation](https://github.com/mintty/mintty/wiki/CtrlSeqs) | Partly | `?2500` and `?2501`, with autodetection on by default; no `?1243` ([documentation](https://github.com/mintty/mintty/wiki/CtrlSeqs#bidirectional-rendering)) |
| VTE | 0.84.1, [source](https://gitlab.gnome.org/GNOME/vte/-/tree/0.84.1) | Yes | all three, with the BiDi recommendation's defaults ([source](https://gitlab.gnome.org/GNOME/vte/-/blob/0.84.1/src/modes.py#L1209-L1235)) |

## Validation

### BIDI-1: A default xterm does not recognize them

Input, one step per line:

```text
\e[?1243$p
\e[?2500$p
\e[?2501$p
```

Expected screen:

```text
|____|
reply \e[?1243;0$y\e[?2500;0$y\e[?2501;0$y
```

### BIDI-2: Nor after they are set

Input, one step per line:

```text
\e[?1243h\e[?2500h\e[?2501h
\e[?1243$p
\e[?2500$p
\e[?2501$p
```

Expected screen:

```text
|____|
reply \e[?1243;0$y\e[?2500;0$y\e[?2501;0$y
```

### BIDI-3: Box drawing is stored as it arrived

Input, one step per line:

```text
\e[?2500h
\u{250C}\u{2500}\u{2510}
```

Expected screen:

```text
|┌─┐_|
cursor 1,4
```

---

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