^[control-codes live in libghostty-vt

Private modes

?1243, ?2500, ?2501 BiDi Modes

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.

?1243 Arrow Key Swapping

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

BiDi in Terminal Emulators 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 are two from ECMA-48, BDSM and SCP, 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.

ModeSetResetDefault
?1243the Left and Right arrow keys are swapped while the cursor is in a right-to-left paragraphthe arrow keys are never swappedset
?2500box drawing characters are mirrored in right-to-left textthey are drawn as they arereset
?2501each paragraph’s direction is detected from its text, falling back on the one SCP setthe direction is the one SCP setreset

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 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 and SD 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, EL or the like splits a paragraph in two, both halves keep its settings.

VTE 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 implements all three, with the defaults above, and answers DECRQM 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). 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). libghostty-vt does the same, and the cases follow xterm.

Support

TerminalVersionSupportNotes
VT220manual, 2nd ed., 1984Nonot in the manual
VT510manual, 1st ed., 1993Nonot in the manual
xtermpatch 412, xterm-411aNonot recognized; DECRQM reports 0
libghostty-vt919dca5Nonot recognized; DECRQM reports 0
ConEmubuild 230724, its documentationNonot in its ANSI escape codes
mintty3.8.3, its documentationPartly?2500 and ?2501, with autodetection on by default; no ?1243 (documentation)
VTE0.84.1, sourceYesall three, with the BiDi recommendation’s defaults (source)

Validation

BIDI-1: A default xterm does not recognize them

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

BIDI-2: Nor after they are set

\e[?1243h\e[?2500h\e[?2501h
\e[?1243$p
\e[?2500$p
\e[?2501$p
|____|
reply \e[?1243;0$y\e[?2500;0$y\e[?2501;0$y

BIDI-3: Box drawing is stored as it arrived

\e[?2500h
\u{250C}\u{2500}\u{2510}
|┌─┐_|
cursor 1,4
Every example on these pages runs in your browser, in libghostty-vt compiled to WebAssembly.