چرا ساختن LSP برای Rust اینقدر سخته
خلاصهٔ کاملتر
نویسنده داره Rust Glancer رو میسازه، یه LSP آزمایشی برای Rust. LSP یا Language Server Protocol پروتکلیه که ادیتور باهاش از یه سرور چیزایی مثل hover، inlay hint و پیدا کردن ارجاعها رو میگیره. حرف اصلی مقاله اینه که LSP باید با اطلاعات ناقص جواب مفید بده. کامپایلر یا کل پروژه رو میسازه یا شکست میخوره، ولی LSP باید از همون ثانیههای اول یه چیز بهدردبخور تحویل بده.
مشکل از همون درخواست initialize شروع میشه. اگه صبر کنی کل پروژه ایندکس بشه، کاربر ۱۰ تا ۱۰۰ ثانیه با ادیتور بیمصرف منتظر میمونه. برای همین هر دو ابزار فقط کانفیگ رو چک میکنن و جواب میدن. بعد، LSP فرض میکنه فقط سند و ویرایش هست، ولی تو Rust به فایلها هم نیاز داری. راهحل یه VFS (فایلسیستم مجازی تو حافظه) با «source generation» هست، یعنی شمارهی نسخهی کد که با هر تغییر بالا میره و کوئریهای قدیمی رو کنسل میکنه. کوئریهای خوندنی هم باید موازی اجرا بشن.
هر کوئری عمق متفاوتی لازم داره. documentSymbol فقط با parse کردن فایل باز جواب داده میشه. hover باید بدونه چه چیزی تو scope دیده میشه، پس به item tree و defmap (نقشهی اینکه هر ماژول چی رو میبینه) نیاز داره. inlay hintها type inference (حدس زدن نوع متغیرها) لازم دارن. سختترینش references هست که باید همهی استفادههای یه چیز مثل Option رو تو هزاران بدنهی تابع پیدا کنه. نویسنده میگه ساختن LSP کلاً دربارهی همین حالتهای افراطیه.
rust-analyzer از salsa استفاده میکنه، یه دیتابیس incremental که خروجیها رو فقط وقتی لازم شد حساب و کش میکنه، و با cache priming لایهی semantic کل workspace رو از قبل آماده میکنه. Rust Glancer برعکس روی مصرف کم RAM و ریاستارت سریع تمرکز داره: همهچیز رو از همون اول یهجا ایندکس میکنه و روی دیسک ذخیره میکنه. ایندکس اولیهش کندتره، ولی بعد از ریاستارت ادیتور تقریباً آنی بالا میاد.
تو مدیریت workspace هم فرق دارن. rust-analyzer پوشههای فرزند رو برای Cargo.toml میگرده و همه رو تو یه پروسه مدیریت میکنه، پس خطای یه workspace کل سرور رو قرمز میکنه و کرش یه crate همهچیز رو میخوابونه. Rust Glancer وجود Cargo.toml رو اجباری میدونه و برای هر workspace یه پروسهی جدا میسازه تا کرش یکی به بقیه سرایت نکنه، هرچند این طراحی پیچیدهتره.
جزئیات ریز هم کم نیستن: پروتکل با خط و ستون و UTF-16 کار میکنه، پس به line index نیاز داری. برای ویرایش باید نوع خطشکنی فایل رو تشخیص بدی. حتی برای parse کردن باید edition پروژه رو بدونی تا بفهمی gen کلمهی کلیدیه یا شناسه. ایندکس هم خطی نیست و با حلقههایی تکرار میشه تا importها و ماکروها کامل resolve بشن.
نکات کلیدی:
- LSP باید با اطلاعات ناقص سریع جواب بده، برخلاف کامپایلر که یا کامل میسازه یا شکست میخوره
- rust-analyzer با salsa بهصورت lazy و incremental کار میکنه و cache priming لایهی semantic رو از قبل میسازه
- Rust Glancer همهچیز رو eager ایندکس میکنه و روی دیسک نگه میداره تا ریاستارت تقریباً آنی باشه
- تو Rust Glancer هر workspace یه پروسهی جداست، پس کرش یکی کل سرور رو نمیخوابونه
- پروتکل LSP با خط، ستون و UTF-16 کار میکنه و سرور باید line index و نوع خطشکنی هر فایل رو نگه داره