feat(tui): native Arabic/RTL (bidi) support for the opencode2 beta - #48590
Open
muhamedbeshir wants to merge 7 commits into
Open
feat(tui): native Arabic/RTL (bidi) support for the opencode2 beta#48590muhamedbeshir wants to merge 7 commits into
muhamedbeshir wants to merge 7 commits into
Conversation
Port of the UAX anomalyco#9 fix to the opencode2 (v2) codebase, which uses @opentui/core 0.5.10. - bidi engine (bidi-js wrapper): first-strong paragraph direction, LRI/PDI isolation for URLs/paths/numbers/code spans, grapheme/width-aware wrapping, logical<->visual caret maps. - BidiTextareaRenderable for the prompt; edit buffer stays logical. - withBidiMarkdown composes with the existing plugins.markdown() renderNode for assistant paragraphs/headings; code/tables/diffs keep the stock LTR renderer. - BidiTextRenderable for user message echoes. - English-only content takes the stock render path; selection/copy stays logical. - Tests: engine unit tests + real test-renderer component tests (31 passing).
Contributor
|
The following comment was made by an LLM, it may be inaccurate: Potential Related PRs Found:
Why they're related: PR #48590 is a port of the bidi support from the dev branch (#48587) to the beta/opencode2 codebase. The PR description states "Refs #38524, #40004, #39525, #32984. The dev-line PR (#48587) carries the auto-closing references; this PR is the same fix for the |
The TUI cannot render with its own font, so /fonts is an application-level bridge: it lists installed font families (recommended Arabic fonts first) and writes the choice into Windows Terminal or the VS Code terminal settings, keeping a timestamped backup. It never installs fonts.
- Wrap the /fonts dialog footer in <text> so the dialog no longer crashes with an orphan-text error and actually opens. - Register BidiTextRenderable as the global ext component so RTL ordering/alignment works in dialogs, lists, tool output and toasts, not only in the prompt and message bodies. English-only content still takes the stock paint path. - Paint reasoning prose through the bidi code renderer so Arabic thinking blocks reorder RTL while code lines without Arabic stay LTR. - Detect the hosting terminal (Windows Terminal vs VS Code) before writing the Arabic font, and preserve existing fallbacks instead of replacing them. - Add coverage for host detection and fallback merging.
Windows Terminal has no font.fallbacks setting; it resolves a comma-separated font.face chain instead. The previous writer stored an array the terminal silently ignored, so picking a font never changed anything. The writer now builds 'Mono, <pick>, <arabic...>' preserving the user's faces, and removes any stale fallbacks key.
Windows Terminal warns about every missing family in the face chain. Limit the tail to families that resolve on this machine (the picked font is always kept); the dialog already labels the rest as not installed.
Drop the /fonts command, its dialog, the settings writer, docs and tests. Font selection stays where it belongs: the terminal's own settings. Bidi rendering itself is unchanged.
6 tasks
- Apply UAX anomalyco#9 L2 flips over the full augmented stream including isolate controls, matching the reference application instead of clamping them away. Clamping rotated words and detached punctuation in mixed runs. - Expand each island across adjacent Latin/digit words so one isolate covers every maximal LTR run; sibling sub-flips plus the whole-line flip otherwise swap neighboring runs. - Broaden island seeds: markdown code spans, spaced Windows paths and key:line references. Code spans containing RTL prose stay natural. - Prefer wrapping outside LTR islands so tokens are not split across visual lines unless unavoidable. - Paint multi-unit graphemes with drawText: native setCell drops Arabic tashkeel. Shared paintBidiCell helper for text, textarea and markdown. - Install markdown bidi paint on CodeRenderable itself so list items, blockquotes and streaming updates inherit it; fenced code and English keep the stock painter. Drop the now-redundant renderNode hook. - Regression tests for mixed Arabic/English/code/path/URL/number runs, lists, links, diffs, wrapping and tashkeel in all three paint paths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Refs #38524, #40004, #39525, #32984. The dev-line PR (#48587) carries the auto-closing references; this PR is the same fix for the
betaline soopencode2behaves the same way.Type of change
What does this PR do?
The
betaline is a different codebase fromdev(packages are named@opencode/*, TUI is@opencode/tui2.0.0, and it pins@opentui/core0.5.10), so this is a port rather than a cherry-pick. It applies the same Unicode Bidirectional Algorithm support (UAX #9) to the interactive TUI:packages/tui/src/util/bidi.ts— bidi engine overbidi-js: paragraph direction from the first strong character (P2/P3), LRI/PDI isolates around LTR islands (URLs, paths, dotted/dashed identifiers, numbers, code spans), grapheme- and width-aware wrapping, and logical<->visual boundary maps.packages/tui/src/component/bidi-textarea.ts—TextareaRenderablesubclass for the prompt: visual caret and visual arrow motion; the edit buffer stays logical, so typing, paste, undo and values are unchanged.packages/tui/src/component/bidi-markdown.ts— addswithBidiMarkdown(base), which composes with the renderNode the TUI already passes (plugins.markdown()); plugin renderables win, and everything else falls through to the bidi paragraph/heading painter. Fenced code, tables and diffs keep the stock LTR renderer.packages/tui/src/component/bidi-text.ts—TextRenderablesubclass for user message echoes.<textarea>-><bidi_textarea>incomponent/prompt/index.tsx;renderNode={withBidiMarkdown(plugins.markdown())}inroutes/session/message-parts.tsxandroutes/session/index.tsx; user message text -><bidi_text>.English-only content takes the stock paint path unchanged, and selection/copy returns the original logical text (isolates are layout-only and are never written to a buffer).
How did you verify your code works?
On Windows 11, on this branch:
Result: 31 pass, 0 fail (220 assertions). The component tests use the real OpenTUI 0.5.10 test renderer and assert the painted cell frame: RTL order, right alignment, fenced code LTR, English-only output identical to the stock renderer, caret geometry, and selection copy returning logical text.
Screenshots / recordings
Text-only change. Two of the exact cell-order frames the tests assert (a real terminal renders them right-to-left because the cells are already in visual order):
Arabic on its own, right-aligned:
Mixed Arabic + English, English run intact:
Checklist