Dil Sunucusu Protokol Eğitimi: VSCode'dan Vim'e

Yayınlanan: 2022-03-11

Tüm çalışmalarınızın ana eseri büyük olasılıkla düz metin dosyalarıdır. Öyleyse neden onları oluşturmak için Not Defteri'ni kullanmıyorsunuz?

Sözdizimi vurgulama ve otomatik biçimlendirme, buzdağının sadece görünen kısmıdır. Linting, kod tamamlama ve yarı otomatik yeniden düzenlemeye ne dersiniz? Bunların hepsi “gerçek” bir kod düzenleyici kullanmak için çok iyi nedenlerdir. Bunlar günlük yaşamımız için hayati öneme sahiptir, ancak nasıl çalıştıklarını anlıyor muyuz?

Bu Dil Sunucusu Protokolü eğitiminde, bu soruları biraz inceleyeceğiz ve metin editörlerimizi neyin işaretlediğini öğreneceğiz. Sonunda, birlikte VSCode, Sublime Text 3 ve Vim için örnek istemcilerle birlikte temel bir dil sunucusu uygulayacağız.

Derleyiciler ve Dil Hizmetleri

Şimdilik, kendi başına ilginç bir konu olan statik analizle ele alınan sözdizimi vurgulamayı ve biçimlendirmeyi atlayacağız ve bu araçlardan aldığımız ana geri bildirimlere odaklanacağız. İki ana kategori vardır: derleyiciler ve dil hizmetleri.

Derleyiciler kaynak kodunuzu alır ve farklı bir biçim verir. Kod dilin kurallarına uymuyorsa, derleyici hata verecektir. Bunlar oldukça tanıdık. Bununla ilgili sorun, genellikle oldukça yavaş ve kapsamının sınırlı olmasıdır. Hala kodu oluştururken yardım sunmaya ne dersiniz?

Dil hizmetlerinin sağladığı şey budur. Hala çalışma halindeyken ve muhtemelen tüm projeyi derlemekten çok daha hızlı bir şekilde kod tabanınız hakkında size fikir verebilirler.

Bu hizmetlerin kapsamı çeşitlidir. Projedeki tüm sembollerin bir listesini döndürmek kadar basit bir şey olabilir veya adımları yeniden düzenleme koduna döndürmek gibi karmaşık bir şey olabilir. Bu hizmetler, kod düzenleyicilerimizi kullanmamızın birincil nedenidir. Sadece derlemek ve hataları görmek isteseydik, bunu birkaç tuşa basarak yapabilirdik. Dil hizmetleri bize daha fazla içgörü sağlıyor ve hem de çok hızlı.

Programlama için bir Metin Düzenleyicisine Bahis Yapmak

Henüz belirli metin editörlerini çağırmadığımıza dikkat edin. Nedenini bir örnekle açıklayalım.

Lapine adında yeni bir programlama dili geliştirdiğinizi varsayalım. Güzel bir dildir ve derleyici müthiş Elm benzeri hata mesajları verir. Ek olarak, kod tamamlama, referanslar, yeniden düzenleme yardımı ve tanılama sağlayabilirsiniz.

İlk önce hangi kod/metin düzenleyiciyi destekliyorsunuz? Peki ya bundan sonra? İnsanların onu benimsemesini sağlamak için zorlu bir savaşınız var, bu yüzden mümkün olduğunca kolaylaştırmak istiyorsunuz. Yanlış düzenleyiciyi seçip kullanıcıları kaçırmak istemezsiniz. Ya kod düzenleyicilerden uzak durup uzmanlığınıza, yani dile ve özelliklerine odaklanırsanız?

Dil Sunucuları

Dil sunucularını girin. Bunlar, dil istemcileriyle konuşan ve bahsettiğimiz içgörüleri sağlayan araçlardır. Varsayımsal durumumuzla az önce açıkladığımız nedenlerden dolayı metin editörlerinden bağımsızdırlar.

Her zamanki gibi, başka bir soyutlama katmanı tam da ihtiyacımız olan şey. Bunlar, dil araçlarının ve kod editörlerinin sıkı bağlantısını kırmayı vaat ediyor. Dil yaratıcıları, özelliklerini bir kez bir sunucuya sarabilir ve kod/metin düzenleyiciler, kendilerini istemciye dönüştürmek için küçük uzantılar ekleyebilir. Bu herkes için bir kazanç. Ancak bunu kolaylaştırmak için, bu istemcilerin ve sunucuların nasıl iletişim kuracağı konusunda anlaşmamız gerekiyor.

Şansımıza, bu varsayımsal değil. Microsoft, Dil Sunucusu Protokolünü tanımlamaya çoktan başladı.

Çoğu büyük fikirde olduğu gibi, öngörüden ziyade zorunluluktan doğdu. Birçok kod düzenleyici, çeşitli dil özellikleri için destek eklemeye çoktan başlamıştı; bazı özellikler üçüncü taraf araçlara dış kaynaklı, bazıları ise editörler içinde kaputun altında yapılır. Ölçeklenebilirlik sorunları ortaya çıktı ve Microsoft, işleri bölme konusunda liderliği aldı. Evet, Microsoft bu özellikleri VSCode içinde biriktirmek yerine kod düzenleyicilerinden çıkarmanın yolunu açtı. Kullanıcıları kilitleyerek editörlerini oluşturmaya devam edebilirlerdi ama onları serbest bıraktılar.

Dil Sunucusu Protokolü

Dil Sunucusu Protokolü (LSP), 2016 yılında dil araçlarını ve düzenleyicileri ayırmaya yardımcı olmak için tanımlanmıştır. Üzerinde hala birçok VSCode parmak izi var, ancak bu, editör agnostisizmi yönünde büyük bir adım. Protokolü biraz inceleyelim.

İstemciler ve sunucular (kod editörlerini ve dil araçlarını düşünün) basit metin mesajlarıyla iletişim kurar. Bu iletilerin HTTP benzeri üstbilgileri, JSON-RPC içeriği vardır ve istemciden veya sunucudan kaynaklanabilir. JSON-RPC protokolü, istekleri, yanıtları ve bildirimleri ve bunların etrafındaki birkaç temel kuralı tanımlar. Anahtar özelliği, asenkron olarak çalışacak şekilde tasarlanmasıdır, böylece istemciler/sunucular düzensiz ve bir dereceye kadar paralellik ile mesajlarla ilgilenebilirler.

Kısacası, JSON-RPC, bir istemcinin başka bir programın parametrelerle bir yöntemi çalıştırmasını istemesine ve bir sonuç veya hata döndürmesine izin verir. LSP bunun üzerine kuruludur ve mevcut yöntemleri, beklenen veri yapılarını ve işlemlerle ilgili birkaç kuralı daha tanımlar. Örneğin, istemci sunucuyu başlattığında bir el sıkışma süreci vardır.

Sunucu durum bilgisine sahiptir ve aynı anda yalnızca tek bir istemciyi işlemek içindir. Bununla birlikte, iletişimde açık bir kısıtlama yoktur, bu nedenle bir dil sunucusu, istemciden farklı bir makinede çalışabilir . Ancak pratikte bu, gerçek zamanlı geri bildirim için oldukça yavaş olacaktır. Dil sunucuları ve istemcileri aynı dosyalarla çalışır ve oldukça konuşkandır.

Ne arayacağınızı öğrendikten sonra LSP'nin yeterli miktarda belgesi vardır. Belirtildiği gibi, fikirlerin çok daha geniş bir uygulaması olmasına rağmen, bunların çoğu VSCode bağlamında yazılmıştır. Örneğin, protokol belirtiminin tamamı TypeScript'te yazılmıştır. VSCode ve TypeScript'e aşina olmayan kaşiflere yardımcı olmak için işte bir başlangıç.

LSP Mesaj Tipleri

Dil Sunucusu Protokolünde tanımlanmış birçok mesaj grubu vardır. Bunlar kabaca “yönetici” ve “dil özellikleri” olarak ikiye ayrılabilir. Yönetici mesajları, istemci/sunucu anlaşmasında, dosyaları açmada/değiştirmede vb. kullanılanları içerir. Daha da önemlisi, burası istemciler ve sunucuların hangi özellikleri kullandıklarını paylaştığı yerdir. Elbette farklı diller ve araçlar farklı özellikler sunar. Bu aynı zamanda aşamalı benimsemeye izin verir. Langserver.org, istemcilerin ve sunucuların desteklemesi gereken, en az birinin listeyi oluşturmak için gerekli olduğu yarım düzine temel özelliği belirtir.

En çok ilgilendiğimiz şey dil özellikleridir. Bunlardan özellikle vurgulanması gereken bir tane var: tanı mesajı. Teşhis, temel özelliklerden biridir. Bir dosyayı açtığınızda, çoğunlukla bunun çalışacağı varsayılır. Editörünüz, dosyada bir sorun olup olmadığını size söylemelidir. Bunun LSP ile gerçekleşme şekli şudur:

  1. İstemci dosyayı açar ve sunucuya textDocument/didOpen gönderir.
  2. Sunucu dosyayı analiz eder ve textDocument/publishDiagnostics bildirimini gönderir.
  3. İstemci sonuçları ayrıştırır ve düzenleyicide hata göstergelerini görüntüler.

Bu, dil hizmetlerinizden fikir edinmenin pasif bir yoludur. Daha aktif bir örnek, imlecinizin altındaki sembol için tüm referansları bulmak olacaktır. Bu şöyle bir şey olur:

  1. İstemci, bir dosyada bir konum belirterek sunucuya textDocument/references gönderir.
  2. Sunucu sembolü bulur, bu ve diğer dosyalardaki referansları bulur ve bir liste ile yanıt verir.
  3. İstemci, kullanıcıya yapılan referansları görüntüler.

Kara Liste Aracı

Dil Sunucusu Protokolünün özelliklerini kesinlikle araştırabiliriz, ancak bunu istemci uygulayıcılarına bırakalım. Düzenleyici ve dil aracı ayrımı fikrini pekiştirmek için araç yaratıcısı rolünü oynayacağız.

Bunu basit tutacağız ve yeni bir dil ve özellikler oluşturmak yerine tanılamaya bağlı kalacağız. Teşhisler uygundur: Bunlar yalnızca bir dosyanın içeriğiyle ilgili uyarılardır. Bir linter, tanılamayı döndürür. Benzer bir şey yapacağız.

Kaçınmak istediğimiz kelimeleri bize bildirecek bir araç yapacağız. Ardından, bu işlevi birkaç farklı metin düzenleyiciye sağlayacağız.

Dil Sunucusu

İlk olarak, araç. Bunu bir dil sunucusuna çevireceğiz. Basit olması açısından, bu bir Node.js uygulaması olacaktır, ancak bunu akışları okuma ve yazma için kullanabilen herhangi bir teknolojiyle yapabiliriz.

İşte mantık. Bir metin verildiğinde, bu yöntem, eşleşen kara listeye alınmış sözcüklerin bir dizisini ve bulundukları yerde dizinleri döndürür.

 const getBlacklisted = (text) => { const blacklist = [ 'foo', 'bar', 'baz', ] const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi') const results = [] while ((matches = regex.exec(text)) && results.length < 100) { results.push({ value: matches[0], index: matches.index, }) } return results }

Şimdi onu bir sunucu yapalım.

 const { TextDocuments, createConnection, } = require('vscode-languageserver') const {TextDocument} = require('vscode-languageserver-textdocument') const connection = createConnection() const documents = new TextDocuments(TextDocument) connection.onInitialize(() => ({ capabilities: { textDocumentSync: documents.syncKind, }, })) documents.listen(connection) connection.listen()

Burada vscode-languageserver kullanıyoruz. VSCode dışında kesinlikle çalışabileceğinden, ad yanıltıcıdır. Bu, LSP'nin kökeni hakkında gördüğünüz birçok “parmak izinden” biridir. vscode-languageserver , alt düzey protokolle ilgilenir ve kullanım durumlarına odaklanmanıza olanak tanır. Bu snippet bir bağlantı başlatır ve onu bir belge yöneticisine bağlar. Bir istemci sunucuya bağlandığında, sunucu ona açılan metin belgelerinin bildirilmesini istediğini söyleyecektir.

Burada durabiliriz. Bu, anlamsız olsa da tamamen işleyen bir LSP sunucusudur. Bunun yerine, bazı tanılama bilgileriyle belge değişikliklerine yanıt verelim.

 documents.onDidChangeContent(change => { connection.sendDiagnostics({ uri: change.document.uri, diagnostics: getDiagnostics(change.document), }) })

Son olarak, değişen belge, mantığımız ve tanılama yanıtı arasındaki noktaları birleştiriyoruz.

 const getDiagnostics = (textDocument) => getBlacklisted(textDocument.getText()) .map(blacklistToDiagnostic(textDocument)) const { DiagnosticSeverity, } = require('vscode-languageserver') const blacklistToDiagnostic = (textDocument) => ({ index, value }) => ({ severity: DiagnosticSeverity.Warning, range: { start: textDocument.positionAt(index), end: textDocument.positionAt(index + value.length), }, message: `${value} is blacklisted.`, source: 'Blacklister', })

Tanılama yükümüz, belgenin metnini işlevimiz aracılığıyla çalıştırmanın sonucu olacak ve ardından istemci tarafından beklenen biçime eşlenecektir.

Bu komut dosyası sizin için tüm bunları yaratacaktır.

 curl -o- https://raw.githubusercontent.com/reergymerej/lsp-article-resources/revision-for-6.0.0/blacklist-server-install.sh | bash

Not: Yabancıların makinenize yürütülebilir dosyalar eklemesinden rahatsızsanız, lütfen kaynağı kontrol edin. Projeyi oluşturur, index.js dosyasını indirir ve sizin için npm link verir.

Yukarıdaki curl komutunun çıktısı, projeyi sizin için kuruyoruz.

Komple Sunucu Kaynağı

Son blacklist-server kaynağı:

 #!/usr/bin/env node const { DiagnosticSeverity, TextDocuments, createConnection, } = require('vscode-languageserver') const {TextDocument} = require('vscode-languageserver-textdocument') const getBlacklisted = (text) => { const blacklist = [ 'foo', 'bar', 'baz', ] const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi') const results = [] while ((matches = regex.exec(text)) && results.length < 100) { results.push({ value: matches[0], index: matches.index, }) } return results } const blacklistToDiagnostic = (textDocument) => ({ index, value }) => ({ severity: DiagnosticSeverity.Warning, range: { start: textDocument.positionAt(index), end: textDocument.positionAt(index + value.length), }, message: `${value} is blacklisted.`, source: 'Blacklister', }) const getDiagnostics = (textDocument) => getBlacklisted(textDocument.getText()) .map(blacklistToDiagnostic(textDocument)) const connection = createConnection() const documents = new TextDocuments(TextDocument) connection.onInitialize(() => ({ capabilities: { textDocumentSync: documents.syncKind, }, })) documents.onDidChangeContent(change => { connection.sendDiagnostics({ uri: change.document.uri, diagnostics: getDiagnostics(change.document), }) }) documents.listen(connection) connection.listen()

Dil Sunucusu Protokol Eğitimi: Test Sürüşü Zamanı

Proje link sonra, taşıma mekanizması olarak stdio belirterek sunucuyu çalıştırmayı deneyin:

 blacklist-server --stdio

Daha önce bahsettiğimiz LSP mesajlarını şimdi stdio dinliyor. Bunları manuel olarak sağlayabiliriz, ancak bunun yerine bir istemci oluşturalım.

Dil İstemcisi: VSCode

Bu teknoloji VSCode'dan geldiği için oradan başlamak uygun görünüyor. Bir LSP istemcisi oluşturacak ve onu az önce yaptığımız sunucuya bağlayacak bir uzantı oluşturacağız.

Bir VSCode uzantısı oluşturmanın, Yeoman'ı ve uygun jeneratörü, generator-code code'u kullanmak da dahil olmak üzere, birkaç yolu vardır. Basitlik için, yine de bir barebone örneği yapalım.

Kazan plakasını klonlayalım ve bağımlılıklarını yükleyelim:

 git clone [email protected]:reergymerej/standalone-vscode-ext.git blacklist-vscode cd blacklist-vscode npm i # or yarn

blacklist-vscode dizinini açın.

Uzantıda hata ayıklayarak başka bir VSCode örneğini başlatmak için F5 tuşuna basın.

İlk VSCode örneğinin "hata ayıklama konsolunda", "Bak, ma. Bir uzantı!"

İki VSCode örneği. Soldaki, blacklist-vscode uzantısını çalıştırıyor ve hata ayıklama konsolu çıktısını gösteriyor ve sağdaki, uzantı geliştirme ana bilgisayarı.

Artık, tüm çanlar ve ıslıklar olmadan çalışan temel bir VSCode uzantısına sahibiz. Bunu bir LSP istemcisi yapalım. Her iki VSCode örneğini kapatın ve blacklist-vscode dizini içinden şunu çalıştırın:

 npm i vscode-languageclient

extension.js dosyasını şununla değiştirin:

 const { LanguageClient } = require('vscode-languageclient') module.exports = { activate(context) { const executable = { command: 'blacklist-server', args: ['--stdio'], } const serverOptions = { run: executable, debug: executable, } const clientOptions = { documentSelector: [{ scheme: 'file', language: 'plaintext', }], } const client = new LanguageClient( 'blacklist-extension-id', 'Blacklister', serverOptions, clientOptions ) context.subscriptions.push(client.start()) }, }

Bu, VSCode içinde bir LSP istemcisi oluşturmak için vscode-languageclient paketini kullanır. vscode-languageserver farklı olarak, bu VSCode ile sıkı bir şekilde bağlanmıştır. Kısacası, bu uzantıda yaptığımız şey bir istemci oluşturmak ve ona önceki adımlarda oluşturduğumuz sunucuyu kullanmasını söylemek. VSCode uzantısının özelliklerini gözden geçirerek, bu LSP istemcisini düz metin dosyaları için kullanmasını söylediğimizi görebiliriz.

Test sürüşü yapmak için blacklist-vscode dizinini açın. Uzantıda hata ayıklayarak başka bir örnek başlatmak için F5 tuşuna basın.

Yeni VSCode örneğinde bir düz metin dosyası oluşturun ve kaydedin. “foo” veya “bar” yazın ve biraz bekleyin. Bunların kara listeye alındığına dair uyarılar göreceksiniz.

Test.txt'nin açık olduğu yeni VSCode örneği, hatanın altını çizerek "foo" ve "bar" ve sorunlar bölmesinde her biri hakkında kara listeye alındıklarını belirten bir mesaj gösteriyor.

Bu kadar! Mantığımızdan herhangi birini yeniden yaratmamız gerekmedi, sadece istemciyi ve sunucuyu koordine ettik.

Başka bir editör için tekrar yapalım, bu sefer Sublime Text 3. İşlem oldukça benzer ve biraz daha kolay olacak.

Dil İstemcisi: Sublime Text 3

İlk önce ST3'ü açın ve komut paletini açın. Editörü bir LSP istemcisi yapmak için bir çerçeveye ihtiyacımız var. “Paket Kontrolü: Paketi Kur” yazın ve enter tuşuna basın. “LSP” paketini bulun ve kurun. Tamamlandığında, LSP istemcilerini belirleme yeteneğine sahibiz. Birçok ön ayar var, ancak bunları kullanmayacağız. Kendimizi yarattık.

Yine komut paletini açın. “Tercihler: LSP Ayarları”nı bulun ve enter tuşuna basın. Bu, LSP paketi için LSP.sublime-settings yapılandırma dosyasını açacaktır. Özel bir istemci eklemek için aşağıdaki yapılandırmayı kullanın.

 { "clients": { "blacklister": { "command": [ "blacklist-server", "--stdio" ], "enabled": true, "languages": [ { "syntaxes": [ "Plain text" ] } ] } }, "log_debug": true }

Bu, VSCode uzantısından tanıdık gelebilir. Bir istemci tanımladık, ona düz metin dosyaları üzerinde çalışmasını söyledik ve dil sunucusunu belirledik.

Ayarları kaydedin, ardından düz bir metin dosyası oluşturun ve kaydedin. “foo” veya “bar” yazın ve bekleyin. Yine, bunların kara listeye alındığına dair uyarılar göreceksiniz. İşlem -mesajların düzenleyicide nasıl görüntülendiği- farklıdır. Ancak işlevimiz aynı. Bu sefer editöre destek eklemek için neredeyse hiçbir şey yapmadık.

Dil "Müşteri": Vim

Bu endişelerin ayrılmasının, özellikleri metin düzenleyiciler arasında paylaşmayı kolaylaştırdığına hala ikna olmadıysanız, aynı işlevi Coc aracılığıyla Vim'e ekleme adımları burada verilmiştir.

Vim'i açın ve :CocConfig , ardından şunu ekleyin:

 "languageserver": { "blacklister": { "command": "blacklist-server", "args": ["--stdio"], "filetypes": ["text"] } }

Tamamlandı.

İstemci-sunucu Ayrımı Dillerin ve Dil Hizmetlerinin Gelişmesini Sağlar

Dil hizmetlerinin sorumluluğunu kullandıkları metin editörlerinden ayırmak açıkça bir kazançtır. Dil özelliği yaratıcılarının uzmanlık alanlarına odaklanmasına ve editör yaratıcılarının da aynısını yapmasına olanak tanır. Bu oldukça yeni bir fikir, ancak evlat edinme yayılıyor.

Artık çalışmak için bir temeliniz olduğuna göre, belki bir proje bulabilir ve bu fikri ilerletmeye yardımcı olabilirsiniz. Editör alev savaşı asla bitmeyecek, ama sorun değil. Dil becerileri belirli editörlerin dışında var olduğu sürece, istediğiniz editörü kullanmakta özgürsünüz.


Microsoft Altın İş Ortağı rozeti.

Bir Microsoft Altın İş Ortağı olarak Toptal, Microsoft uzmanlarından oluşan seçkin ağınızdır. İhtiyacınız olan uzmanlarla yüksek performanslı ekipler oluşturun - her yerde ve tam olarak ihtiyacınız olduğunda!