Tutorial de protocol de server de limbă: de la VSCode la Vim

Publicat: 2022-03-11

Principalul artefact al tuturor lucrărilor dvs. sunt cel mai probabil fișierele text simplu. Deci, de ce nu folosiți Notepad pentru a le crea?

Evidențierea sintaxelor și formatarea automată sunt doar vârful aisbergului. Ce zici de listing, completarea codului și refactorizarea semi-automată? Toate acestea sunt motive foarte bune pentru a folosi un editor de cod „adevărat”. Acestea sunt vitale pentru zi cu zi, dar înțelegem cum funcționează?

În acest tutorial Language Server Protocol, vom explora puțin aceste întrebări și vom afla ce anume determină editorii noștri de text. În cele din urmă, împreună vom implementa un server de limbă de bază împreună cu exemple de clienți pentru VSCode, Sublime Text 3 și Vim.

Compilatoare vs. Servicii lingvistice

Vom sări peste evidențierea și formatarea sintaxei pentru moment, care sunt gestionate cu analiză statică - un subiect interesant în sine - și ne vom concentra pe feedback-ul principal pe care îl primim de la aceste instrumente. Există două categorii principale: compilatoare și servicii lingvistice.

Compilatorii preiau codul sursă și scuipă o formă diferită. Dacă codul nu respectă regulile limbajului, compilatorul va returna erori. Acestea sunt destul de familiare. Problema cu aceasta este că este, de obicei, destul de lent și limitat în domeniul de aplicare. Ce ziceți de a oferi asistență în timp ce încă creați codul?

Aceasta este ceea ce oferă serviciile lingvistice. Ele vă pot oferi informații despre baza de cod în timp ce este încă în lucru și, probabil, mult mai rapid decât compilarea întregului proiect.

Domeniul de aplicare al acestor servicii este variat. Poate fi ceva la fel de simplu ca returnarea unei liste cu toate simbolurile din proiect sau ceva complex, cum ar fi returnarea pașilor pentru refactorizarea codului. Aceste servicii sunt motivul principal pentru care folosim editorii noștri de cod. Dacă am vrut doar să compilam și să vedem erori, am putea face asta cu câteva apăsări de taste. Serviciile lingvistice ne oferă mai multe informații și foarte rapid.

Pariați pe un editor de text pentru programare

Observați că nu am chemat încă editori de text specifici. Să explicăm de ce cu un exemplu.

Să presupunem că ai dezvoltat un nou limbaj de programare numit Lapine. Este un limbaj frumos, iar compilatorul oferă mesaje de eroare grozave asemănătoare cu Elm. În plus, puteți furniza completarea codului, referințe, ajutor pentru refactorizare și diagnosticare.

Ce editor de cod/text acceptați mai întâi? Ce zici după aceea? Ai o luptă dificilă pentru a-i face pe oameni să-l adopte, așa că vrei să-l faci cât mai ușor posibil. Nu doriți să alegeți editorul greșit și să pierdeți utilizatori. Ce se întâmplă dacă vă păstrați distanța față de editorii de cod și vă concentrați pe specialitatea dvs. - limbajul și caracteristicile sale?

Servere de limbă

Introduceți serverele de limbă . Acestea sunt instrumente care vorbesc cu clienții lingvistici și oferă informațiile pe care le-am menționat. Sunt independenți de editorii de text din motivele pe care tocmai le-am descris cu situația noastră ipotetică.

Ca de obicei, un alt strat de abstractizare este exact ceea ce avem nevoie. Acestea promit să rupă cuplajul strâns dintre instrumentele lingvistice și editorii de cod. Creatorii de limbi străine își pot împacheta funcțiile într-un server o dată, iar editorii de cod/text pot adăuga mici extensii pentru a se transforma în clienți. Este o victorie pentru toată lumea. Pentru a facilita acest lucru, totuși, trebuie să cădem de acord asupra modului în care acești clienți și servere vor comunica.

Din fericire pentru noi, acest lucru nu este ipotetic. Microsoft a început deja prin a defini protocolul Language Server.

Ca și în cazul majorității ideilor grozave, a apărut mai degrabă din necesitate decât din previziune. Mulți editori de cod au început deja să adauge suport pentru diferite caracteristici ale limbii; unele funcții externalizate către instrumente terțe, unele realizate sub capotă în cadrul editorilor. Au apărut probleme de scalabilitate, iar Microsoft a preluat conducerea împărțirii lucrurilor. Da, Microsoft a deschis calea pentru a muta aceste funcții din editorii de cod, mai degrabă decât pentru a le depozita în VSCode. Ar fi putut continua să-și construiască editorul, blocând utilizatori, dar i-au eliberat.

Protocolul serverului de limbă

Protocolul Language Server Protocol (LSP) a fost definit în 2016 pentru a ajuta la separarea instrumentelor lingvistice și a editorilor. Există încă multe amprente VSCode pe el, dar este un pas major în direcția agnosticismului editorului. Să examinăm puțin protocolul.

Clienții și serverele - gândiți-vă la editorii de cod și instrumentele de limbă - comunică prin mesaje text simple. Aceste mesaje au antete asemănătoare HTTP, conținut JSON-RPC și pot proveni fie de la client, fie de la server. Protocolul JSON-RPC definește cererile, răspunsurile și notificările și câteva reguli de bază în jurul acestora. O caracteristică cheie este că este proiectat să funcționeze asincron, astfel încât clienții/serverele să poată face față mesajelor din ordine și cu un anumit grad de paralelism.

Pe scurt, JSON-RPC permite unui client să solicite unui alt program să ruleze o metodă cu parametri și să returneze un rezultat sau o eroare. LSP se bazează pe aceasta și definește metodele disponibile, structurile de date așteptate și alte câteva reguli în jurul tranzacțiilor. De exemplu, există un proces de strângere de mână atunci când clientul pornește serverul.

Serverul are stare și este menit să gestioneze un singur client la un moment dat. Totuși, nu există restricții explicite privind comunicarea, așa că un server de limbă ar putea rula pe o altă mașină decât clientul. În practică, acest lucru ar fi destul de lent pentru feedback în timp real, totuși. Serverele și clienții de limbă funcționează cu aceleași fișiere și sunt destul de vorbăreț.

LSP-ul are o cantitate decentă de documentație odată ce știi ce să cauți. După cum am menționat, multe dintre acestea sunt scrise în contextul VSCode, deși ideile au o aplicație mult mai largă. De exemplu, specificația protocolului este scrisă în TypeScript. Pentru a ajuta exploratorii care nu sunt familiarizați cu VSCode și TypeScript, iată o instrucțiune.

Tipuri de mesaje LSP

Există multe grupuri de mesaje definite în protocolul Language Server. Acestea pot fi împărțite aproximativ în „administrator” și „funcții de limbă”. Mesajele de administrator le conțin pe cele utilizate în strângerea de mână client/server, deschiderea/modificarea fișierelor etc. Este important, aici, clienții și serverele împărtășesc caracteristicile pe care le gestionează. Cu siguranță, diferite limbi și instrumente oferă caracteristici diferite. Acest lucru permite, de asemenea, adoptarea progresivă. Langserver.org numește o jumătate de duzină de caracteristici cheie pe care clienții și serverele ar trebui să le suporte, dintre care cel puțin una este necesară pentru a face lista.

Caracteristicile lingvistice sunt ceea ce ne interesează cel mai mult. Dintre acestea, există una pe care trebuie să o semnalăm în mod specific: mesajul de diagnostic. Diagnosticarea este una dintre caracteristicile cheie. Când deschideți un fișier, se presupune că acesta va rula. Editorul dvs. ar trebui să vă spună dacă este ceva în neregulă cu fișierul. Modul în care se întâmplă acest lucru cu LSP este:

  1. Clientul deschide fișierul și trimite textDocument/didOpen către server.
  2. Serverul analizează fișierul și trimite notificarea textDocument/publishDiagnostics .
  3. Clientul analizează rezultatele și afișează indicatori de eroare în editor.

Acesta este un mod pasiv de a obține informații de la serviciile dvs. lingvistice. Un exemplu mai activ ar fi găsirea tuturor referințelor pentru simbolul de sub cursor. Asta ar merge cam de genul:

  1. Clientul trimite textDocument/references către server, specificând o locație într-un fișier.
  2. Serverul descoperă simbolul, localizează referințe în acest fișier și în alte fișiere și răspunde cu o listă.
  3. Clientul afișează referințele către utilizator.

Un instrument pe lista neagră

Cu siguranță am putea să sapă în specificul protocolului Language Server, dar să lăsăm asta pentru implementatorii clienți. Pentru a consolida ideea de separare a editorului și a instrumentelor lingvistice, vom juca rolul de creator de instrumente.

Vom păstra totul simplu și, în loc să creăm un nou limbaj și caracteristici, ne vom ține de diagnosticare. Diagnosticele sunt potrivite: sunt doar avertismente despre conținutul unui fișier. Un linter returnează diagnostice. Vom face ceva asemanator.

Vom face un instrument pentru a ne anunța cuvintele pe care am dori să le evităm. Apoi, vom oferi această funcționalitate la câteva editoare de text diferite.

Serverul de limbă

În primul rând, instrumentul. Vom introduce asta direct într-un server de limbă. Pentru simplitate, aceasta va fi o aplicație Node.js, deși am putea face acest lucru cu orice tehnologie capabilă să folosească fluxuri pentru citire și scriere.

Aici este logica. Având în vedere ceva text, această metodă returnează o matrice a cuvintelor din lista neagră potrivite și indicii în care au fost găsite.

 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 }

Acum, să facem din el un server.

 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()

Aici, utilizăm vscode-languageserver . Numele este înșelător, deoarece poate funcționa cu siguranță în afara VSCode. Aceasta este una dintre multele „amprente” pe care le vedeți despre originile LSP. vscode-languageserver are grijă de protocolul de nivel inferior și vă permite să vă concentrați asupra cazurilor de utilizare. Acest fragment începe o conexiune și o leagă într-un manager de documente. Când un client se conectează la server, acesta îi va spune că ar dori să fie notificat despre deschiderea documentelor text.

Ne-am putea opri aici. Acesta este un server LSP pe deplin funcțional, deși inutil. În schimb, să răspundem la modificările documentului cu câteva informații de diagnosticare.

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

În cele din urmă, conectăm punctele dintre documentul care s-a schimbat, logica noastră și răspunsul de diagnosticare.

 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', })

Sarcina noastră de diagnosticare va fi rezultatul rulării textului documentului prin funcția noastră, apoi mapat la formatul așteptat de client.

Acest script va crea toate acestea pentru tine.

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

Notă: Dacă nu vă simțiți confortabil cu străinii care adaugă executabile pe computer, vă rugăm să verificați sursa. Acesta creează proiectul, descarcă index.js și npm link îl face pentru tine.

Ieșirea comenzii curl de mai sus, instalând proiectul pentru dvs.

Sursă server completă

Sursa finală blacklist-server este:

 #!/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()

Tutorial privind protocolul serverului lingvistic: este timpul pentru un test drive

După ce proiectul este link , încercați să rulați serverul, specificând stdio ca mecanism de transport:

 blacklist-server --stdio

Acum ascultă pe stdio mesajele LSP despre care am vorbit înainte. Le-am putea furniza manual, dar haideți să creăm un client.

Client de limbă: VSCode

Deoarece această tehnologie își are originea în VSCode, pare oportun să începem de acolo. Vom crea o extensie care va crea un client LSP și îl va conecta la serverul pe care tocmai l-am creat.

Există mai multe moduri de a crea o extensie VSCode, inclusiv folosind Yeoman și generatorul corespunzător, generator-code . Pentru simplitate, totuși, să facem un exemplu simplu.

Să clonăm boilerplate și să instalăm dependențele sale:

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

Deschideți directorul blacklist-vscode în VSCode.

Apăsați F5 pentru a porni o altă instanță VSCode, depanând extensia.

În „consola de depanare” a primei instanțe VSCode, veți vedea textul „Uite, ma. O extensie!"

Două instanțe VSCode. Cel din stânga rulează extensia blacklist-vscode și arată rezultatul consolei de depanare, iar cel din dreapta este gazda de dezvoltare a extensiei.

Acum avem o extensie VSCode de bază care funcționează fără toate clopotele. Să-l transformăm într-un client LSP. Închideți ambele instanțe VSCode și din directorul blacklist-vscode , rulați:

 npm i vscode-languageclient

Înlocuiți extension.js cu:

 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()) }, }

Aceasta utilizează pachetul vscode-languageclient pentru a crea un client LSP în VSCode. Spre deosebire vscode-languageserver , acesta este strâns cuplat la VSCode. Pe scurt, ceea ce facem în această extensie este să creăm un client și să îi spunem să folosească serverul creat în pașii anteriori. Trecând peste specificul extensiei VSCode, putem vedea că îi spunem să folosească acest client LSP pentru fișiere text simplu.

Pentru a-l testa, deschideți directorul blacklist-vscode în VSCode. Apăsați F5 pentru a porni o altă instanță, depanând extensia.

În noua instanță VSCode, creați un fișier text simplu și salvați-l. Tastați „foo” sau „bar” și așteptați un moment. Veți vedea avertismente că acestea sunt pe lista neagră.

Noua instanță VSCode cu test.txt deschis, afișând „foo” și „bar” cu subliniere a erorilor și un mesaj despre fiecare în panoul de probleme, spunând că sunt pe lista neagră.

Asta e! Nu a trebuit să recreăm logica noastră, doar să coordonăm clientul și serverul.

Să o facem din nou pentru un alt editor, de data aceasta Sublime Text 3. Procesul va fi destul de asemănător și puțin mai ușor.

Limbă client: Sublime Text 3

Mai întâi, deschideți ST3 și deschideți paleta de comenzi. Avem nevoie de un cadru pentru a face din editor un client LSP. Tastați „Package Control: Install Package” și apăsați Enter. Găsiți pachetul „LSP” și instalați-l. Odată finalizat, avem capacitatea de a specifica clienții LSP. Există multe presetări, dar nu le vom folosi. Ne-am creat pe al nostru.

Din nou, deschideți paleta de comenzi. Găsiți „Preferințe: Setări LSP” și apăsați Enter. Aceasta va deschide fișierul de configurare, LSP.sublime-settings , pentru pachetul LSP. Pentru a adăuga un client personalizat, utilizați configurația de mai jos.

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

Acest lucru poate părea familiar din extensia VSCode. Am definit un client, i-am spus să funcționeze pe fișiere text simplu și am specificat serverul de limbă.

Salvați setările, apoi creați și salvați un fișier text simplu. Tastați „foo” sau „bar” și așteptați. Din nou, veți vedea avertismente că acestea sunt pe lista neagră. Tratamentul - modul în care mesajele sunt afișate în editor - este diferit. Cu toate acestea, funcționalitatea noastră este aceeași. De data asta abia dacă am făcut ceva pentru a adăuga suport pentru editor.

Limba „Client”: Vim

Dacă încă nu sunteți convins că această separare a preocupărilor facilitează partajarea funcțiilor între editorii de text, iată pașii pentru a adăuga aceeași funcționalitate la Vim prin Coc.

Deschideți Vim și tastați :CocConfig , apoi adăugați:

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

Terminat.

Separarea client-server permite limbilor și serviciilor lingvistice să prospere

Separarea responsabilității serviciilor lingvistice de editorii de text în care sunt utilizate este în mod clar un câștig. Le permite creatorilor de caracteristici lingvistice să se concentreze pe specialitatea lor, iar creatorilor editorilor să facă același lucru. Este o idee destul de nouă, dar adopția se răspândește.

Acum că aveți o bază de la care să lucrați, poate puteți găsi un proiect și puteți ajuta la promovarea acestei idei. Războiul flăcării editorului nu se va termina niciodată, dar asta e în regulă. Atâta timp cât abilitățile lingvistice pot exista în afara unor editori specifici, sunteți liber să utilizați orice editor doriți.


Insigna Microsoft Gold Partner.

În calitate de partener de aur Microsoft, Toptal este rețeaua dvs. de elită de experți Microsoft. Construiți echipe de înaltă performanță cu experții de care aveți nevoie - oriunde și exact când aveți nevoie de ei!