Prior work
Several projects already attack parts of this problem. Before building anything, we read the code of each one, not just its README, and mapped what it actually does onto our nine layers.
Layer coverage
Each row is a project; each column is one of our nine layers. Read across a row to see what a project covers, and down a column to see who has touched a layer.
| 1Encoding | 2Input | 3Width | 4Shaping | 5Bidi | 6Cursor & copy | 7Fonts | 8Multiplexers | 9Apps & TUI | |
|---|---|---|---|---|---|---|---|---|---|
| RtlTerminal | ◐Partial | ◐Partial | ◐Partial | ●Solid | ◐Partial | ●Solid | ◐Partial | ·Absent | ◐Partial |
| termenal-web | ·Absent | ◐Partial | ◐Partial | ●Solid | ◐Partial | ●Solid | ◐Partial | ·Absent | ◐Partial |
| termux-app-arabic | ·Absent | ·Absent | ◐Partial | ●Solid | ◐Partial | ◐Partial | ·Absent | ·Absent | ·Absent |
| Tarminal | ?Not in the repo | ?Not in the repo | ?Not in the repo | ?Not in the repo | ·Absent | ?Not in the repo | ◐Partial | ·Absent | ?Not in the repo |
| Terminal-ar | ·Absent | ·Absent | ·Absent | ◐Partial | ◐Partial | ◐Partial | ◐Partial | ·Absent | ◐Partial |
| ar-terminal | ·Absent | ◐Partial | ·Absent | ◐Partial | ◐Partial | ◐Partial | ◐Partial | ·Absent | ◐Partial |
| estaterm | ·Absent | ◐Partial | ·Absent | ◐Partial | ◐Partial | ·Absent | ·Absent | ·Absent | ◐Partial |
| bidiscope | ·Absent | ·Absent | ·Absent | ◐Partial | ◐Partial | ◐Partial | ·Absent | ·Absent | ◐Partial |
| fa-console | ●Solid | ◐Partial | ·Absent | ◐Partial | ◐Partial | ◐Partial | ◐Partial | ·Absent | ·Absent |
| rtl-terminal | ·Absent | ·Absent | ·Absent | ·Absent | ◐Partial | ·Absent | ·Absent | ·Absent | ◐Partial |
The projects
RtlTerminal
Terminal emulator · Windows (WPF + ConPTY)
- Shaping
- WPF, per word
- Bidi
- Hand-rolled heuristic
The most serious prior art: logical buffer, logical copy and visual→logical mouse mapping. But its bidi isn't UAX #9, and it squashes words into cells.
Read the notes →termenal-web
Browser terminal (prototype) · Browser + Node PTY
- Shaping
- HarfBuzz, per bidi run
- Bidi
- UAX #9 levels; paragraph by majority
Best reference design: a per-row logical↔visual map with logical copy. The WASM core and WebGPU renderer it claims are not in the code.
Read the notes →termux-app-arabic
Terminal emulator (fork) · Android
- Shaping
- Platform HarfBuzz, full line
- Bidi
- UAX #9 per row, base forced LTR
A ~300-line renderer proof of concept. Touch and selection are not mapped back, there are no tests, and it is applied unconditionally.
Read the notes →Tarminal
Terminal emulator (SwiftTerm shell) · macOS
- Shaping
- None in repo
- Bidi
- None
A wrapper with no Arabic, bidi or width code of its own. The thing to study is SwiftTerm.
Read the notes →Terminal-ar
Overlay over xterm.js (Electron) · Windows / Linux
- Shaping
- Chromium, per span
- Bidi
- Heuristic, per span
Not reusable (claims MIT, ships no license). The mirrored cursor disagrees with the text, and there are no tests.
Read the notes →ar-terminal
VS Code overlay + PTY transform · VS Code
- Shaping
- Chromium / presentation forms
- Bidi
- Blink per row / bidi-js baked in
Not reusable (no license). Its main path keeps copy logical but misaligns cursor and selection; its fallback path is the visual-order hack.
Read the notes →estaterm
PTY proxy · Linux
- Shaping
- FriBidi → presentation forms
- Bidi
- UAX #9 (FriBidi), per chunk
The textbook visual-order hack, and it steps aside on the alternate screen. Only its FriBidi call sequence is worth reusing.
Read the notes →bidiscope
Library + xterm.js overlay · JS / browser
- Shaping
- Hand table → presentation forms
- Bidi
- Partial UAX #9 (no BD13)
Right instinct (an overlay keeps the buffer logical), wrong execution. Its 99.91 % conformance claim is unverified.
Read the notes →fa-console
Python stdout wrapper · Windows console
- Shaping
- arabic-reshaper → presentation forms
- Bidi
- python-bidi or whole-line reversal
Reusable for layer 1 (UTF-8 setup, ي/ك → ی/ک folding). Its display path is a visual-order hack that corrupts ANSI.
Read the notes →rtl-terminal
Claude Code plugin (prompt only) · Claude Code
- Shaping
- None
- Bidi
- Asks the model to emit controls
Markdown only: no hook, no detection, nothing enforced. Worth taking: isolating paths with LRI…PDI, and its test corpus.
Read the notes →What the field tells us
- 1
Rewriting the text always costs
Every tool that wrote presentation forms or visual order into the terminal broke copy, search or escape sequences, and gave up on full-screen apps. The renderers that kept a logical buffer got copy right.
- 2
Width is unsolved
No project fits shaped Persian to the cell grid. Glyphs are squashed, clipped, or allowed to overflow. This is the core research question.
- 3
Multiplexers: 0 of 10
Every terminal-side implementation treats a physical row as the bidi paragraph, so a tmux or Zellij split would merge RTL runs across pane borders. Nobody has looked.
- 4
Nobody negotiates who owns bidi
All of them apply bidi unconditionally or by guesswork, including inside full-screen apps. None uses the explicit modes of the BiDi in Terminal Emulators draft.
- 5
Input is ignored
No project handles IME composition, ZWNJ entry or logical cursor movement while typing.
- 6
READMEs overstate
Five projects claim more than their code does, and two claim MIT without a license file. Our scorecard measures; it never repeats claims, including our own.
Worth reusing
| From | What | Layers |
|---|---|---|
| termenal-web | Per-row colToVisual / visualToCol permutation; shaping per bidi run with explicit direction | 5 · 6 |
| RtlTerminal | Logical selection offsets and visual→logical mouse mapping, with tests; zero width for marks and ZWNJ | 3 · 6 |
| termux-app-arabic | Shape whole runs with full-line context; draw the cursor as an overlay; force an LTR base so reordering stays local | 4 · 5 |
| fa-console | UTF-8 setup; ي/ك → ی/ک and digit folding; the rule that program data stays logical | 1 |
| estaterm | The FriBidi call sequence (not its output path) | 5 |
| rtl-terminal | LRI…PDI isolation around paths and code; a before/after test corpus | 5 · tests |
Foundations and history
Section titled “Foundations and history”- Neovim #553, “Bidi language support”: open since 2014, with 47 comments. The problem is more than a decade old.
- BiDi in Terminal Emulators (Egmont Koblinger, 2019): the draft standard for who owns bidi, the terminal or the app. VTE implements it; none of the ten projects use it.
- Engines: HarfBuzz for shaping; FriBidi and ICU for UAX #9. Every project that got shaping right delegated it to an engine like these.