RtlTerminal
- مخزن: https://github.com/mirbehnam/RtlTerminal · کامیت خواندهشده:
a5be441(۲۰۲۶-۰۹-۲۷، کلون سطحی) · مجوز: MIT (LICENSE، «PersianTerminal contributors»؛ بخش عربی README هنوز میگوید فایل مجوزی وجود ندارد) · زبان: C# (.NET 8، WPF) · سکو: Windows 10 1809+ / 11 (ConPTY) - دسته: شبیهساز ترمینال (terminal emulator)
این پروژه چیست
Section titled “این پروژه چیست”یک شبیهساز ترمینال مستقل برای ویندوز (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.
- راستبهچپِ قوی = هر نویسه از ردهٔ حروف در بازههای U+0590–08FF، FB1D–FDFF، FE70–FEFF، 10800–10FFF، 1E800–1EEFF (
- جهت پاراگراف / تراز: برای هر خط جداگانه. حالت 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).
پوشش لایهها
Section titled “پوشش لایهها”| لایه | وضعیت | شواهد |
|---|---|---|
| ۱ رمزگذاری و یکسانسازی | ◐ | رمزگشایی/رمزگذاری 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 میشکنند |
ادعاشده اما تأییدنشده
Section titled “ادعاشده اما تأییدنشده”- «حفظ جهت درست تکههای انگلیسی، اعداد و نشانهگذاری» — فقط در چارچوب قواعد سادهشدهٔ بالا؛ ارقام فارسی مثل حروف 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؛ هیچ سطح کتابخانهای قابل استفادهٔ مجدد ندارد (همهچیز درون خود برنامه است).