رفتن به محتوا

RtlTerminal

  • مخزن: https://github.com/mirbehnam/RtlTerminal · کامیت خوانده‌شده: a5be441 (۲۰۲۶-۰۹-۲۷، کلون سطحی) · مجوز: MIT (LICENSE، «PersianTerminal contributors»؛ بخش عربی README هنوز می‌گوید فایل مجوزی وجود ندارد) · زبان: C# (.NET 8، WPF) · سکو: Windows 10 1809+ / 11 (ConPTY)
  • دسته: شبیه‌ساز ترمینال (terminal emulator)

یک شبیه‌ساز ترمینال مستقل برای ویندوز (WPF + ConPTY) که یک توسعه‌دهندهٔ ایرانی ساخته، با این هدف مشخص که خروجی فارسی و عربیِ شل‌ها و ابزارهای خط فرمان هوش مصنوعی (Codex، Claude، OpenCode) با حروف پیوسته و به ترتیبی خوانا نمایش داده شود. پارسر VT، بافر سلول‌ها و رندرکنندهٔ ردیف‌ها همه مال خود پروژه است. حدود ۵٫۴ هزار خط کد C# برای برنامه و حدود ۸۰۰ خط تست دارد؛ نسخهٔ 1.0.7 (RtlTerminal.csproj:25).

چگونه کار می‌کند (تأییدشده در کد)

Section titled “چگونه کار می‌کند (تأییدشده در کد)”
  • خط لوله: ConPTY (ConPtySession.cs:65، ورودی/خروجی UTF-8 در :179) ← پارسر VT دست‌نویس + شبکهٔ منطقی سلول‌ها (TerminalBuffer.cs) ← یک TerminalSnapshot تغییرناپذیر از TerminalLineها که از اجراهای (run) سبک‌دار تشکیل شده‌اند ← TerminalView فقط ردیف‌های قابل‌مشاهده را چیدمان و رسم می‌کند (TerminalView.cs:283-350، :376-417).
  • بافر منطقی می‌ماند. سلول‌ها به ترتیب رسیدن نوشته می‌شوند (TerminalBuffer.cs:695-776) و هیچ بازچینی‌ای در بافر انجام نمی‌شود. تمام کار مربوط به جهت، یک تبدیل نما در زمان رندر است.
  • دوجهتی (bidi) = یک روش حدسی دست‌ساز (SmartRtl.cs):
    • راست‌به‌چپِ قوی = هر نویسه از ردهٔ حروف در بازه‌های U+0590–08FF، FB1D–FDFF، FE70–FEFF، 10800–10FFF، 1E800–1EEFF (SmartRtl.cs:240-260). هر حرف دیگر و هر رقم (از جمله ارقام عربی-هندی و فارسی) چپ‌به‌راستِ قوی حساب می‌شود (:98-118).
    • پرانتزها و کروشه‌های جفتی ()[]{}<> اگر محتوایشان یک‌جهت باشد، جهت محتوا را می‌گیرند (:120-192) — نسخهٔ ساده‌شدهٔ قاعدهٔ N0.
    • اجراهای خنثی: اگر دو طرف هم‌جهت باشند ← همان جهت؛ وگرنه اگر فاصلهٔ سفید نداشته باشند و به متن چپ‌به‌راست چسبیده باشند ← چپ‌به‌راست؛ وگرنه جهت پایهٔ پاراگراف (:194-238) — نسخهٔ ساده‌شدهٔ N1/N2.
    • خروجی فهرستی تخت از بازه‌های LTR/RTL است — بدون سطوح جاسازی (embedding levels)، بدون نویسه‌های قالب‌بندی صریح (LRI/RLI/FSI/PDI، LRE/RLE…)، بدون رسیدگی به اعداد طبق W1 تا W7، و بدون بازنشانی فاصله‌های سفید طبق L1.
  • جهت پاراگراف / تراز: برای هر خط جداگانه. حالت Smart RTL (به‌طور پیش‌فرض روشن) هر خطی را که حتی یک حرف راست‌به‌چپِ قوی داشته باشد، با پایهٔ راست‌به‌چپ در نظر می‌گیرد و راست‌چین می‌کند (SmartRtl.cs:18-26، TerminalView.cs:292-295). حالت «Row RTL» برای همهٔ ردیف‌ها پایهٔ راست‌به‌چپ و راست‌چینی را تحمیل می‌کند. با پایهٔ RTL فهرست بازه‌ها معکوس می‌شود (TerminalView.cs:311)؛ درون یک بازهٔ RTL سلول‌ها از راست به چپ چیده می‌شوند (:320).
  • پهنا / سلول‌ها: خوشه‌های گرافیمی با StringInfo.GetTextElementEnumerator تشخیص داده می‌شوند؛ پهنا = بیشینهٔ GetCellWidth روی rune‌ها (TerminalView.cs:302-308). GetCellWidth (TerminalBuffer.cs:797-816): Mn/Mc/Me = 0، ZWSP/ZWNJ/ZWJ/WJ/BOM و همهٔ Cf = 0، یک جدول بازه‌ای سخت‌کدشده برای نویسه‌های شرق آسیا و ایموجی = 2 (:821-835)، و بقیه 1. حروف عربی = ۱ سلول؛ اعراب (حرکات) به سلول قبلی می‌چسبند (AppendCombiningCharacter، :778-790).
  • شکل‌دهی (shaping): هر بازهٔ RTL به تکه‌های فاصلهٔ سفید / غیرفاصله تقسیم می‌شود؛ هر کلمه به‌صورت یک FormattedText واحد در WPF با FlowDirection.RightToLeft رسم می‌شود (TerminalView.cs:325-345، :453-458)، پس پیوستن حروف، لیگاتورها و جایگزینی قلم (font fallback) را پشتهٔ متنی WPF انجام می‌دهد. سبک‌ها روی زیربازه‌ها اعمال می‌شوند، بنابراین تغییر رنگ ANSI وسط یک کلمه پیوستگی حروف را نمی‌شکند (:459-472). فاصله‌ها دقیقاً به پهنای سلول باقی می‌مانند.
  • ترفند تطبیق پهنا: پیشروی (advance) طبیعیِ کلمهٔ شکل‌داده‌شده با یک ScaleTransform به‌صورت افقی کشیده یا فشرده می‌شود تا دقیقاً بودجهٔ سلولی‌اش را پر کند (TerminalView.cs:493، :510). سپس محدوده‌های برخورد (hit box) هر نویسه از BuildHighlightGeometry روی متن شکل‌داده‌شده دوباره استخراج می‌شوند (:495-508).
  • گلیف‌های (glyph) لاتین و کادرکشی سلول‌به‌سلول و دقیقاً روی مرز سلول‌ها رسم می‌شوند (:323-324)؛ نویسه‌های کادر و بلوک به‌صورت هندسی رسم می‌شوند (DrawBlock/DrawBox).
  • انتخاب و کپی: لنگرهای انتخاب به‌شکل (row, logical UTF-16 offset) هستند (TerminalView.cs:19، :271-281)؛ هایلایت هر سلولی را که بازهٔ منطقی‌اش انتخاب شده، هر جا که از نظر دیداری قرار گرفته باشد، رنگ می‌کند (:401-407)؛ GetSelectedText رشتهٔ منطقی ردیف را برش می‌دهد (:191-205). کپی به ترتیب منطقی است. یک تست این را بررسی می‌کند (tests/RtlTerminal.RenderTests/Program.cs:167-168).
  • مکان‌نما (cursor): CursorColumn منطقی را به سلول دیداری‌ای که آن را می‌پوشاند نگاشت می‌کند (TerminalView.cs:408-415).
  • گزارش ماوس به TUIها: مختصات X دیداری از طریق همان چیدمان به یک ستون منطقی برگردانده می‌شود و سپس برای برنامه فرستاده می‌شود (TerminalView.cs:148-174، با توضیح «TUI applications receive logical terminal columns»).
  • صفحهٔ جایگزین (alternate screen) (vim، htop، OpenCode): راست‌چینی غیرفعال می‌شود، ولی بازچینی بازه‌ها درون شبکه همچنان انجام می‌شود (SmartRtl.ShouldRightAlign؛ تست رندر Program.cs:231-236 بررسی می‌کند که ترتیب در صفحهٔ جایگزین با صفحهٔ اصلی یکی باشد).
  • ورودی: متن رسیده از PreviewTextInput در WPF (خروجی چیدمان صفحه‌کلید/IME سیستم‌عامل) بی‌کم‌وکاست به‌صورت UTF-8 فرستاده می‌شود (MainWindow.xaml.cs:365-373)؛ کلیدهای پردازش‌شده توسط IME باز می‌شوند (:1279). یک اصلاح برای چیدمان فارسی: Shift+/ وقتی زبان ورودی fa است، U+061F ؟ می‌فرستد (:1247-1256). میان‌بری برای نیم‌فاصله (ZWNJ) ندارد.
  • پوشش VT: حالت‌های خصوصی DEC شمارهٔ 1، 6، 7، 25، 47، 1000/1002/1003/1004/1006، 1047–1049، 2004، 2026 (TerminalBuffer.cs:651-652)؛ چسباندن براکتی (bracketed paste)، ماوس SGR، گزارش فوکوس و خروجی همگام.
  • قلم‌ها: هر قلم نصب‌شده روی ویندوز؛ ایموجی اجباراً با Segoe UI Emoji (TerminalView.cs:455)؛ در غیر این صورت جایگزینی قلم WPF (docs/renderer.md).
لایه وضعیت شواهد
۱ رمزگذاری و یکسان‌سازی ◐ رمزگشایی/رمزگذاری UTF-8 در مرز PTY (ConPtySession.cs:179، MainWindow.xaml.cs:1532)؛ هیچ‌جا یکسان‌سازی (normalization) یونیکد انجام نمی‌شود
۲ ورودی ◐ متن IME سیستم‌عامل عبور داده می‌شود (MainWindow.xaml.cs:365)؛ اصلاح ؟ فارسی (:1247)؛ کلید میان‌بر نیم‌فاصله ندارد، کمکی برای حرکت منطقی مکان‌نما ندارد
۳ پهنا ◐ آگاه از گرافیم، نشانه‌ها و ZWNJ = 0 (TerminalBuffer.cs:797)؛ جدول سخت‌کدشدهٔ نویسه‌های پهن، بدون همگام‌سازی EAW/نسخهٔ یونیکد با برنامه‌ها؛ پهنای متن شکل‌داده‌شده با کشیدن به تعداد سلول‌ها تحمیل می‌شود
۴ شکل‌دهی ✅ FormattedText در WPF با جهت RTL برای هر کلمه، تفکیک سبک بدون شکستن پیوستگی (TerminalView.cs:325-345، :453-472)؛ بدون جایگزینی با فرم‌های نمایشی (presentation forms)
۵ دوجهتی ◐ روش حدسی دست‌ساز، نه UAX #9 (SmartRtl.cs)؛ ترمینال بی‌قیدوشرط مالک آن است — حتی در صفحهٔ جایگزین؛ هیچ دنبالهٔ گریزی برای خاموش‌کردنش نیست (حالت 2500 و غیره)
۶ مکان‌نما، انتخاب و کپی ✅ آفست‌های منطقی برای انتخاب و کپی، همراه با تست (TerminalView.cs:191-205؛ RenderTests/Program.cs:163-168)؛ مکان‌نما و گزارش‌های ماوس از دیداری به منطقی نگاشت می‌شوند (:148-174، :408-415)
۷ قلم‌ها ◐ قلم کاربر + جایگزینی WPF + Segoe UI Emoji؛ زنجیرهٔ جایگزینی ویژهٔ فارسی ندارد
۸ مالتی‌پلکسرها ❌ هیچ آگاهی‌ای از tmux/zellij ندارد؛ کل صفحهٔ یک مالتی‌پلکسر به‌صورت ردیف‌های صفحهٔ جایگزین دیده می‌شود و ردیف‌به‌ردیف بازچینی می‌شود (که مرز پنل‌ها را در میان پنل‌ها می‌شکند)
۹ برنامه‌ها و چارچوب‌های TUI ◐ TUIهای مبتنی بر ConPTY اجرا می‌شوند؛ مختصات ماوس ترجمه می‌شود؛ اما بازچینی دوجهتی درون شبکهٔ برنامه یعنی فرض‌های ستونیِ TUI در ردیف‌های RTL می‌شکنند
  • «حفظ جهت درست تکه‌های انگلیسی، اعداد و نشانه‌گذاری» — فقط در چارچوب قواعد ساده‌شدهٔ بالا؛ ارقام فارسی مثل حروف LTR رفتار می‌شوند و جداکننده‌های عددی (1,234، ۱۲/۳۴) هیچ قاعدهٔ W ندارند.
  • درستی در استفادهٔ واقعی با OpenCode/Claude/Codex: خودِ docs/renderer.md می‌گوید تست‌های دودی (smoke test) «do not replace interactive checks … IME input, clipboard operations, multiple DPI settings, and different fonts».
  • تست‌های رندر بیرون از صفحه فایل PNG تولید می‌کنند؛ هیچ مبنای مقایسهٔ پیکسلی (pixel-diff baseline) بررسی نشد (اینجا قابل اجرا نیست — Windows/WPF).

آنچه می‌آموزیم یا برمی‌داریم

Section titled “آنچه می‌آموزیم یا برمی‌داریم”
  • بافر منطقی، رندر دیداری، کپی منطقی — تفکیک درست، و تمیز پیاده‌شده: لنگرهای انتخاب بر حسب آفست منطقی، و هایلایت برای هر سلول دیداری رنگ می‌شود. الگوی خوبی برای لایهٔ ۶.
  • ترجمهٔ ماوس از دیداری به منطقی پیش از گزارش به برنامه (TerminalView.cs:148) — جزئیاتی که بیشتر تلاش‌ها برای ترمینال RTL از قلم می‌اندازند.
  • شکل‌دهی برای هر کلمه، سبک برای هر زیربازه، تا تغییر SGR وسط کلمه پیوستگی حروف عربی را نشکند — مستقیماً به لایهٔ شکل‌دهی ما مربوط است.
  • فاصله‌ها را دقیقاً به پهنای سلول نگه دار در حین شکل‌دادن کلمات — جلوی کش‌آمدن فاصله‌ها با قلم‌های جایگزینِ تناسبی را می‌گیرد.
  • نمونه‌های تست: «real Codex Persian output with ZWNJ»، «mixed-text selection round-trips logically»، «alternate screen does not change ordering» (tests/RtlTerminal.RenderTests/Program.cs:111-170، :216-240) — ارزش دارد این‌ها را به‌عنوان مورد به پیکرهٔ انطباق (conformance corpus) خودمان اضافه کنیم.
  • نگاشت مجدد ? به ؟ در چیدمان فارسی، یک گرفتاری کوچک ولی واقعی در لایهٔ ۲ است که باید فهرست شود.

محدودیت‌ها و الگوهای نادرست

Section titled “محدودیت‌ها و الگوهای نادرست”
  • دوجهتی ابتکاری به‌جای UAX #9 (بدون سطوح، بدون جداسازها (isolates)، بدون قواعد اعداد) — دقیقاً همان چیزی که اصول ما رد می‌کند؛ باید ICU/FriBidi/unicode-bidi باشد.
  • دوجهتیِ سمت ترمینال به برنامه‌های تمام‌صفحه تحمیل می‌شود و هیچ مذاکره‌ای دربارهٔ حالت BiDi وجود ندارد (نه ECMA-48 SCP/BDSM، نه حالت DEC 2500/2501). برنامه‌ای که خودش دوجهتی را انجام می‌دهد (یا جدولی با سلول‌های RTL می‌کشد) دوبار بازچینی می‌شود؛ موقعیت مکان‌نمای vim و ستون‌های سلول از نظر دیداری از هم فاصله می‌گیرند.
  • «هر حرف RTL ⇒ پاراگراف RTL + راست‌چینی» برای هر ردیف فیزیکی: یک خط منطقیِ شکسته‌شده (wrapped) ممکن است میان ردیف‌ها ترازش عوض شود؛ یک کلمهٔ عربی در یک خط لاگ انگلیسی کل خط را برمی‌گرداند.
  • کشیدن افقی گلیف‌ها برای جا دادن متن شکل‌داده‌شده در n × cellWidth — شکل حروف را معوج می‌کند (کلمات فشرده یا کشیده)؛ نشانه‌ای است از این‌که پهنا (لایهٔ ۳) با برنامه توافق نشده و فقط ماست‌مالی شده است.
  • بازه‌های سخت‌کدشده برای نویسه‌های پهن، بدون هم‌ترازی نسخهٔ یونیکد با wcwidth در سمت برنامه.
  • فقط Windows/WPF؛ هیچ سطح کتابخانه‌ای قابل استفادهٔ مجدد ندارد (همه‌چیز درون خود برنامه است).

یک پروژهٔ مهدی بانشی · mbaneshi.ir