Dil Sunucusu Protokolü

Dil Sunucusu Protokolü nedir?

Bir düzenleyicide veya IDE'de bir programlama dili için kaynak kodu otomatik tamamlamaları veya Tanıma Git gibi zengin düzenleme özelliklerini desteklemek geleneksel olarak çok zordur ve zaman alır. Genellikle düzenleyicinin veya IDE'nin programlama dilinde bir etki alanı modeli (tarayıcı, ayrıştırıcı, tür denetleyicisi, oluşturucu ve daha fazlası) yazmayı gerektirir. Örneğin Eclipse IDE'de C/C++ desteği sağlayan Eclipse CDT eklentisi, Eclipse IDE'nin kendisi Java'da yazıldığı için Java'da yazılır. Bu yaklaşımın uygulanması, Visual Studio Code için TypeScript'te bir C/C++ etki alanı modeli ve Visual Studio için C# içinde ayrı bir etki alanı modeli uygulamak anlamına gelir.

Bir geliştirme aracı mevcut dile özgü kitaplıkları yeniden kullanabiliyorsa dile özgü etki alanı modelleri oluşturmak da çok daha kolaydır. Ancak, bu kitaplıklar genellikle programlama dilinin kendisinde uygulanır (örneğin, iyi C/C++ etki alanı modelleri C/C++'da uygulanır). C/C++ kitaplığını TypeScript'te yazılmış bir düzenleyiciyle tümleştirmek teknik olarak mümkündür ancak yapılması zordur.

Dil sunucuları

Bir diğer yaklaşım da kitaplığı kendi sürecinde çalıştırmak ve onunla konuşmak için işlemler arası iletişimi kullanmaktır. İleri geri gönderilen iletiler bir protokol oluşturur. Dil sunucusu protokolü (LSP), bir geliştirme aracı ile dil sunucusu işlemi arasında değiştirilen iletileri standartlaştırmanın ürünüdür. Dil sunucularını veya şeytanları kullanmak yeni veya yeni bir fikir değildir. Vim ve Emacs gibi düzenleyiciler, anlamsal otomatik tamamlama desteği sağlamak için bir süredir bunu yapıyor. LSP'nin amacı bu tür tümleştirmeleri basitleştirmek ve dil özelliklerini çeşitli araçlara sunmak için kullanışlı bir çerçeve sağlamaktı.

Ortak bir protokole sahip olmak, dilin etki alanı modelinin mevcut bir uygulamasını yeniden kullanarak programlama dili özelliklerinin en az telaşla bir geliştirme aracıyla tümleştirilmesine olanak tanır. Dil sunucusu arka ucu PHP, Python veya Java ile yazılabilir ve LSP çeşitli araçlarla kolayca tümleştirilmesini sağlar. Protokol, bir aracın temel alan modeline özgü nüansları tam olarak anlamak zorunda kalmadan zengin dil hizmetleri sunabilmesi için ortak bir soyutlama düzeyinde çalışır.

LSP üzerinde çalışma nasıl başlatıldı?

LSP zaman içinde gelişti ve bugün Sürüm 3.0'da. Bir dil sunucusu kavramı, C# için zengin düzenleme özellikleri sağlamak üzere OmniSharp tarafından alındığında başladı. Başlangıçta OmniSharp, JSON yüküyle HTTP protokolunu kullandı ve Visual Studio Code dahil olmak üzere çeşitli düzenleyicilerle tümleştirilmiştir.

Aynı zamanda Microsoft, Emacs ve Sublime Text gibi düzenleyicilerde TypeScript'i destekleme fikriyle bir TypeScript dil sunucusu üzerinde çalışmaya başladı. Bu uygulamada bir düzenleyici, TypeScript sunucu işlemiyle stdin/stdout üzerinden iletişim kurar ve istekler ve yanıtlar için V8 hata ayıklayıcı protokolünden esinlenen bir JSON yükü kullanır. TypeScript sunucusu, zengin TypeScript düzenlemesi için TypeScript Sublime eklentisi ve VS Code ile tümleştirilmiş.

İki farklı dil sunucusunu tümleştirdikten sonra VS Code ekibi, düzenleyiciler ve IDE'ler için ortak dil sunucusu protokollerini keşfetmeye başladı. Ortak protokol, dil sağlayıcısının farklı IDE'ler tarafından kullanılabilecek tek bir dil sunucusu oluşturmasını sağlar. Dil sunucusu tüketicisinin protokolün istemci tarafını yalnızca bir kez uygulaması gerekir. Bu, hem dil sağlayıcısı hem de dil tüketicisi için win-win durumuna neden olur.

Dil sunucusu protokolü, TypeScript sunucusu tarafından kullanılan protokolle başladı ve BUNU VS Code dil API'lerinden ilham alan daha fazla dil özelliğiyle genişletildi. Protokol, basitliği ve mevcut kitaplıkları nedeniyle uzaktan çağırma için JSON-RPC ile desteklenir.

VS Code ekibi, bir dosya lint (tarama) isteklerine yanıt veren ve algılanan bir dizi uyarı ve hata döndüren birkaç linter dil sunucusu uygulayarak protokolün prototipini oluşturmaçtı. Amaç, kullanıcı belgede düzenledikçe bir dosyayı lint etmekti. Bu, düzenleyici oturumu sırasında birçok linting isteği olacağı anlamına gelir. Her kullanıcı düzenlemesi için yeni bir linting işleminin başlatılması gerekmemesi için sunucuyu çalışır durumda tutmak mantıklıdır. VS Code'un ESLint ve TSLint uzantıları da dahil olmak üzere çeşitli linter sunucuları uygulandı. Bu iki linter sunucusu hem TypeScript/JavaScript'te uygulanır hem de Node.js üzerinde çalıştırılır. Protokolün istemci ve sunucu bölümünü uygulayan bir kitaplığı paylaşırlar.

LSP nasıl çalışır?

Dil sunucusu kendi işleminde çalışır ve Visual Studio veya VS Code gibi araçlar JSON-RPC üzerinden dil protokolünü kullanarak sunucuyla iletişim kurar. Ayrılmış bir işlemde çalışan dil sunucusunun bir diğer avantajı da tek bir işlem modeliyle ilgili performans sorunlarının önlenmesidir. Gerçek aktarım kanalı, istemci ve sunucu Node.js'de yazılmışsa stdio, yuva, adlandırılmış kanallar veya düğüm ipc olabilir.

Aşağıda, bir aracın ve dil sunucusunun düzenli bir düzenleme oturumu sırasında nasıl iletişim kurarak iletişim kurması için bir örnek verilmiştir:

lsp flow diagram

  • Kullanıcı araçta bir dosya (belge olarak adlandırılır) açar: Araç, bir belgenin açık olduğunu dil sunucusuna bildirir ('textDocument/didOpen'). Bundan sonra, belgenin içeriği hakkındaki gerçek artık dosya sisteminde değil, araç tarafından bellekte tutulmaktadır.

  • Kullanıcı düzenlemeler yapar: Araç, sunucuya belge değişikliğini ('textDocument/didChange') bildirir ve programın anlamsal bilgileri dil sunucusu tarafından güncelleştirilir. Bu durumda, dil sunucusu bu bilgileri analiz eder ve aracı algılanan hatalar ve uyarılarla ('textDocument/publishDiagnostics') uyarır.

  • Kullanıcı düzenleyicideki bir simgede "Tanıma Git"i yürütür: Araç iki parametre içeren bir 'textDocument/definition' isteği gönderir: (1) belge URI'si ve (2) Tanıma Git isteğinin sunucuya başlatıldığı metin konumu. Sunucu, belge URI'siyle ve simge tanımının belge içindeki konumuyla yanıt verir.

  • Kullanıcı belgeyi (dosya) kapatır: Araçtan bir 'textDocument/didClose' bildirimi gönderilir ve dil sunucusuna belgenin artık bellekte olmadığını ve geçerli içeriğin dosya sisteminde güncel olduğunu bildirir.

Bu örnekte, protokolün "Tanıma Git", "Tüm Başvuruları Bul" gibi düzenleyici özellikleri düzeyinde dil sunucusuyla nasıl iletişim kuracakları gösterilmektedir. Protokol tarafından kullanılan veri türleri, şu anda açık olan metin belgesi ve imlecin konumu gibi düzenleyici veya IDE 'veri türleri'dir. Veri türleri genellikle soyut söz dizimi ağaçları ve derleyici simgeleri (örneğin, çözümlenmiş türler, ad alanları, ...) sağlayacak bir programlama dili etki alanı modeli düzeyinde değildir. Bu, protokolü önemli ölçüde basitleştirir.

Şimdi 'textDocument/definition' isteğine daha ayrıntılı bir şekilde bakalım. Aşağıda, bir C++ belgesindeki "Tanıma Git" isteği için istemci aracı ile dil sunucusu arasında giden yükleri bulabilirsiniz.

İstek şudur:

{
    "jsonrpc": "2.0",
    "id" : 1,
    "method": "textDocument/definition",
    "params": {
        "textDocument": {
            "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/use.cpp"
        },
        "position": {
            "line": 3,
            "character": 12
        }
    }
}

Yanıt şu şekildedir:

{
    "jsonrpc": "2.0",
    "id": "1",
    "result": {
        "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/provide.cpp",
        "range": {
            "start": {
                "line": 0,
                "character": 4
            },
            "end": {
                "line": 0,
                "character": 11
            }
        }
    }
}

Geçmişe dönük olarak, veri türlerini programlama dili modeli düzeyinde değil düzenleyici düzeyinde tanımlamak, dil sunucusu protokolünün başarısının nedenlerinden biridir. Bir metin belgesi URI'sini veya imleç konumunu standartlaştırmak, soyut söz dizimi ağacını ve derleyici simgelerini farklı programlama dillerinde standart hale getirmekle karşılaştırıldığında çok daha basittir.

Kullanıcı farklı dillerle çalışırken VS Code genellikle her programlama dili için bir dil sunucusu başlatır. Aşağıdaki örnekte kullanıcının Java ve SASS dosyaları üzerinde çalıştığı bir oturum gösterilmektedir.

java and sass

Özellikler

Her dil sunucusu protokol tarafından tanımlanan tüm özellikleri desteklemez. Bu nedenle, istemci ve sunucu desteklenen özellik kümesini 'özellikler' aracılığıyla duyurur. Örneğin, bir sunucu 'textDocument/definition' isteğini işleyebileceğini duyurur, ancak 'çalışma alanı/sembol' isteğini işleyemeyebilir. Benzer şekilde, istemciler belge kaydedilmeden önce 'kaydetmek üzere' bildirimleri sağlayabileceklerini duyurabilir, böylece bir sunucu düzenlenen belgeyi otomatik olarak biçimlendirmek için metinsel düzenlemeleri hesaplayabilir.

Dil sunucusunu tümleştirme

Bir dil sunucusunun belirli bir araçla gerçek tümleştirmesi, dil sunucusu protokolü tarafından tanımlanmaz ve araç uygulayıcılarına bırakılır. Bazı araçlar, herhangi bir dil sunucusu türünü başlatabilen ve konuşabilen bir uzantıya sahip olarak dil sunucularını genel olarak tümleştirir. VS Code gibi diğerleri, bir uzantının bazı özel dil özellikleri sağlayabilmesi için dil sunucusu başına özel bir uzantı oluşturur.

Dil sunucularının ve istemcilerin uygulanmasını basitleştirmek için, istemci ve sunucu bölümleri için kitaplıklar veya SDK'lar vardır. Bu kitaplıklar farklı diller için sağlanır. Örneğin, bir dil sunucusunun VS Code uzantısıyla tümleştirilmesini kolaylaştırmak için bir dil istemcisi npm modülü ve Node.js kullanarak bir dil sunucusu yazmak için başka bir dil sunucusu npm modülü vardır. Bu, geçerli destek kitaplıkları listesidir .

Visual Studio'da Dil Sunucusu Protokolü Kullanma