Tutorial sul protocollo del server di lingua: da VSCode a Vim
Pubblicato: 2022-03-11L'artefatto principale di tutto il tuo lavoro sono molto probabilmente file di testo normale. Allora perché non usi Blocco note per crearli?
L'evidenziazione della sintassi e la formattazione automatica sono solo la punta dell'iceberg. Che dire di linting, completamento del codice e refactoring semiautomatico? Questi sono tutti ottimi motivi per utilizzare un editor di codice "reale". Questi sono vitali per il nostro quotidiano, ma capiamo come funzionano?
In questo tutorial sul protocollo del server linguistico, esploreremo un po' queste domande e scopriremo cosa fa funzionare i nostri editor di testo. Alla fine, implementeremo insieme un server di lingua di base insieme a client di esempio per VSCode, Sublime Text 3 e Vim.
Compilatori e servizi linguistici
Per ora salteremo l'evidenziazione della sintassi e la formattazione, che viene gestita con l'analisi statica, un argomento interessante a sé stante, e ci concentreremo sul feedback principale che riceviamo da questi strumenti. Esistono due categorie principali: compilatori e servizi linguistici.
I compilatori prendono il tuo codice sorgente e sputano una forma diversa. Se il codice non segue le regole del linguaggio, il compilatore restituirà errori. Questi sono abbastanza familiari. Il problema con questo è che di solito è piuttosto lento e di portata limitata. Che ne dici di offrire assistenza mentre stai ancora creando il codice?
Questo è ciò che forniscono i servizi linguistici. Possono darti informazioni dettagliate sulla tua base di codice mentre è ancora in lavorazione e probabilmente molto più velocemente rispetto alla compilazione dell'intero progetto.
La portata di questi servizi è varia. Può essere qualcosa di semplice come restituire un elenco di tutti i simboli nel progetto o qualcosa di complesso come restituire i passaggi al codice di refactoring. Questi servizi sono il motivo principale per cui utilizziamo i nostri editor di codice. Se volessimo semplicemente compilare e vedere gli errori, potremmo farlo con pochi tasti. I servizi linguistici ci danno più approfondimenti e molto rapidamente.
Scommettere su un editor di testo per la programmazione
Si noti che non abbiamo ancora chiamato specifici editor di testo. Spieghiamo perché con un esempio.
Supponiamo che tu abbia sviluppato un nuovo linguaggio di programmazione chiamato Lapine. È un bel linguaggio e il compilatore fornisce fantastici messaggi di errore simili a Elm. Inoltre, puoi fornire il completamento del codice, i riferimenti, la guida per il refactoring e la diagnostica.
Quale codice/editor di testo supporti per primo? E dopo? Hai una battaglia in salita che combatte per convincere le persone ad adottarlo, quindi vuoi renderlo il più semplice possibile. Non vuoi scegliere l'editor sbagliato e perdere gli utenti. E se mantieni le distanze dagli editor di codice e ti concentri sulla tua specialità: il linguaggio e le sue caratteristiche?
Server di lingua
Immettere i server di lingua . Questi sono strumenti che parlano ai clienti linguistici e forniscono le informazioni che abbiamo menzionato. Sono indipendenti dagli editor di testo per i motivi che abbiamo appena descritto con la nostra ipotetica situazione.
Come al solito, un altro livello di astrazione è proprio quello di cui abbiamo bisogno. Questi promettono di rompere lo stretto accoppiamento tra strumenti linguistici ed editor di codice. I creatori di lingua possono racchiudere le loro funzionalità in un server una volta e gli editor di codice/testo possono aggiungere piccole estensioni per trasformarsi in client. È una vittoria per tutti. Per facilitare ciò, tuttavia, dobbiamo concordare come comunicheranno questi client e server.
Fortunatamente per noi, questo non è ipotetico. Microsoft ha già iniziato definendo il Language Server Protocol.
Come per la maggior parte delle grandi idee, è nata per necessità piuttosto che per previsione. Molti editor di codice avevano già iniziato ad aggiungere il supporto per varie funzionalità del linguaggio; alcune funzionalità sono state esternalizzate a strumenti di terze parti, altre sono state eseguite in modo nascosto all'interno degli editori. Si sono verificati problemi di scalabilità e Microsoft ha preso l'iniziativa di dividere le cose. Sì, Microsoft ha spianato la strada per spostare queste funzionalità fuori dagli editor di codice anziché accumularle all'interno di VSCode. Avrebbero potuto continuare a creare il loro editor, bloccando gli utenti, ma li hanno liberati.
Protocollo del server di lingua
Il Language Server Protocol (LSP) è stato definito nel 2016 per aiutare a separare gli strumenti linguistici e gli editor. Ci sono ancora molte impronte di VSCode su di esso, ma è un passo importante nella direzione dell'agnosticismo dell'editore. Esaminiamo un po' il protocollo.
Client e server, ad esempio editor di codice e strumenti linguistici, comunicano tramite semplici messaggi di testo. Questi messaggi hanno intestazioni simili a HTTP, contenuto JSON-RPC e possono provenire dal client o dal server. Il protocollo JSON-RPC definisce richieste, risposte e notifiche e alcune regole di base che le riguardano. Una caratteristica fondamentale è che è progettato per funzionare in modo asincrono, in modo che client/server possano gestire i messaggi fuori servizio e con un certo grado di parallelismo.
In breve, JSON-RPC consente a un client di richiedere a un altro programma di eseguire un metodo con parametri e restituire un risultato o un errore. LSP si basa su questo e definisce i metodi disponibili, le strutture dati previste e alcune altre regole relative alle transazioni. Ad esempio, c'è un processo di handshake quando il client avvia il server.
Il server è con stato e pensato solo per gestire un singolo client alla volta. Tuttavia, non ci sono restrizioni esplicite sulla comunicazione, quindi un server di lingua potrebbe essere eseguito su una macchina diversa dal client. In pratica, però, sarebbe piuttosto lento per il feedback in tempo reale. I server e i client delle lingue funzionano con gli stessi file e sono piuttosto loquaci.
LSP ha una discreta quantità di documentazione una volta che sai cosa cercare. Come accennato, gran parte di questo è scritto nel contesto di VSCode, sebbene le idee abbiano un'applicazione molto più ampia. Ad esempio, le specifiche del protocollo sono tutte scritte in TypeScript. Per aiutare gli esploratori che non hanno familiarità con VSCode e TypeScript, ecco un primer.
Tipi di messaggi LSP
Esistono molti gruppi di messaggi definiti nel protocollo del server di lingua. Possono essere approssimativamente suddivisi in "admin" e "funzioni linguistiche". I messaggi di amministrazione contengono quelli utilizzati nell'handshake client/server, nell'apertura/modifica di file, ecc. È importante sottolineare che è qui che client e server condividono le funzionalità che gestiscono. Certamente, lingue e strumenti diversi offrono funzionalità diverse. Ciò consente anche un'adozione incrementale. Langserver.org nomina una mezza dozzina di funzionalità chiave che client e server dovrebbero supportare, almeno una delle quali è necessaria per fare l'elenco.
Le caratteristiche della lingua sono ciò che ci interessa maggiormente. Di queste, ce n'è una da richiamare in modo specifico: il messaggio diagnostico. La diagnostica è una delle caratteristiche principali. Quando si apre un file, si presume principalmente che verrà eseguito. Il tuo editor dovrebbe dirti se c'è qualcosa di sbagliato nel file. Il modo in cui questo accade con LSP è:
- Il client apre il file e invia
textDocument/didOpen
al server. - Il server analizza il file e invia la notifica
textDocument/publishDiagnostics
. - Il client analizza i risultati e visualizza gli indicatori di errore nell'editor.
Questo è un modo passivo per ottenere informazioni dai tuoi servizi linguistici. Un esempio più attivo sarebbe trovare tutti i riferimenti per il simbolo sotto il cursore. Questo sarebbe qualcosa del tipo:
- Il client invia
textDocument/references
al server, specificando una posizione in un file. - Il server individua il simbolo, individua i riferimenti in questo e altri file e risponde con un elenco.
- Il client visualizza i riferimenti all'utente.
Uno strumento per la lista nera
Potremmo sicuramente approfondire le specifiche del protocollo del server di lingua, ma lasciamo questo per gli implementatori del client. Per cementare l'idea di separazione tra editor e strumenti linguistici, giocheremo il ruolo di creatori di strumenti.
Lo manterremo semplice e, invece di creare un nuovo linguaggio e funzionalità, ci atterremo alla diagnostica. La diagnostica è una buona soluzione: sono solo avvisi sul contenuto di un file. Un linter restituisce la diagnostica. Faremo qualcosa di simile.
Realizzeremo uno strumento per segnalarci le parole che vorremmo evitare. Quindi, forniremo quella funzionalità a un paio di diversi editor di testo.
Il server linguistico
Primo, lo strumento. Lo inforcheremo direttamente in un server di lingua. Per semplicità, questa sarà un'app Node.js, anche se potremmo farlo con qualsiasi tecnologia in grado di utilizzare i flussi per leggere e scrivere.
Ecco la logica. Dato del testo, questo metodo restituisce un array di parole nella lista nera abbinate e gli indici in cui sono state trovate.
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 }
Ora, facciamolo diventare 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()
Qui stiamo utilizzando il vscode-languageserver
. Il nome è fuorviante, poiché può certamente funzionare al di fuori di VSCode. Questa è una delle tante “impronte digitali” che vedi delle origini di LSP. vscode-languageserver
si occupa del protocollo di livello inferiore e ti consente di concentrarti sui casi d'uso. Questo frammento di codice avvia una connessione e la collega a un gestore documenti. Quando un client si connette al server, il server gli dirà che desidera essere informato dell'apertura di documenti di testo.

Potremmo fermarci qui. Questo è un server LSP completamente funzionante, anche se inutile. Invece, rispondiamo alle modifiche del documento con alcune informazioni diagnostiche.
documents.onDidChangeContent(change => { connection.sendDiagnostics({ uri: change.document.uri, diagnostics: getDiagnostics(change.document), }) })
Infine, colleghiamo i punti tra il documento che è cambiato, la nostra logica e la risposta diagnostica.
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', })
Il nostro payload diagnostico sarà il risultato dell'esecuzione del testo del documento attraverso la nostra funzione, quindi mappato nel formato previsto dal cliente.
Questo script creerà tutto ciò per te.
curl -o- https://raw.githubusercontent.com/reergymerej/lsp-article-resources/revision-for-6.0.0/blacklist-server-install.sh | bash
Nota: se ti senti a disagio con estranei che aggiungono eseguibili al tuo computer, controlla la fonte. Crea il progetto, scarica index.js
e npm link
lo fa per te.
Sorgente server completa
L'ultima fonte blacklist-server
è:
#!/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()
Esercitazione sul protocollo del server linguistico: tempo per un test drive
Dopo che il progetto è stato link
, prova a eseguire il server, specificando stdio
come meccanismo di trasporto:
blacklist-server --stdio
Sta ascoltando su stdio
ora i messaggi LSP di cui abbiamo parlato prima. Potremmo fornirli manualmente, ma creiamo invece un client.
Cliente della lingua: VSCode
Poiché questa tecnologia ha avuto origine in VSCode, sembra appropriato iniziare da lì. Creeremo un'estensione che creerà un client LSP e lo collegheremo al server che abbiamo appena creato.
Esistono diversi modi per creare un'estensione VSCode, incluso l'utilizzo di Yeoman e del generatore appropriato, generator-code
. Per semplicità, però, facciamo un esempio barebone.
Cloniamo il boilerplate e installiamo le sue dipendenze:
git clone [email protected]:reergymerej/standalone-vscode-ext.git blacklist-vscode cd blacklist-vscode npm i # or yarn
Apri la directory blacklist-vscode
in VSCode.
Premere F5 per avviare un'altra istanza VSCode, eseguendo il debug dell'estensione.
Nella prima "console di debug" della prima istanza VSCode vedrai il testo "Guarda, ma. Un'estensione!"
Ora abbiamo un'estensione VSCode di base che funziona senza tutti i campanelli e i fischietti. Facciamolo diventare un client LSP. Chiudi entrambe le istanze VSCode e dalla directory blacklist-vscode
, esegui:
npm i vscode-languageclient
Sostituisci extension.js con:
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()) }, }
Questo utilizza il pacchetto vscode-languageclient
per creare un client LSP all'interno di VSCode. A differenza vscode-languageserver
, questo è strettamente accoppiato a VSCode. In breve, ciò che stiamo facendo in questa estensione è creare un client e dirgli di utilizzare il server che abbiamo creato nei passaggi precedenti. Trascurando le specifiche dell'estensione VSCode, possiamo vedere che gli stiamo dicendo di utilizzare questo client LSP per i file di testo normale.
Per testarlo, apri la directory blacklist-vscode
in VSCode. Premere F5 per avviare un'altra istanza, eseguendo il debug dell'estensione.
Nella nuova istanza VSCode, crea un file di testo normale e salvalo. Digita "foo" o "bar" e attendi un momento. Vedrai avvisi che questi sono nella lista nera.
Questo è tutto! Non abbiamo dovuto ricreare nessuna delle nostre logiche, ma solo coordinare il client e il server.
Facciamolo di nuovo per un altro editor, questa volta Sublime Text 3. Il processo sarà abbastanza simile e un po' più semplice.
Cliente di lingua: testo sublime 3
Innanzitutto, apri ST3 e apri la tavolozza dei comandi. Abbiamo bisogno di un framework per rendere l'editor un client LSP. Digita "Controllo pacchetto: installa pacchetto" e premi invio. Trova il pacchetto "LSP" e installalo. Una volta completato, abbiamo la possibilità di specificare i client LSP. Ci sono molti preset, ma non li useremo. Abbiamo creato il nostro.
Ancora una volta, apri la tavolozza dei comandi. Trova "Preferenze: Impostazioni LSP" e premi invio. Questo aprirà il file di configurazione, LSP.sublime-settings
, per il pacchetto LSP. Per aggiungere un client personalizzato, utilizzare la configurazione seguente.
{ "clients": { "blacklister": { "command": [ "blacklist-server", "--stdio" ], "enabled": true, "languages": [ { "syntaxes": [ "Plain text" ] } ] } }, "log_debug": true }
Questo può sembrare familiare dall'estensione VSCode. Abbiamo definito un client, gli abbiamo detto di lavorare su file di testo normale e specificato il server della lingua.
Salva le impostazioni, quindi crea e salva un file di testo normale. Digita "foo" o "bar" e attendi. Di nuovo, vedrai avvisi che questi sono nella lista nera. Il trattamento, il modo in cui i messaggi vengono visualizzati nell'editor, è diverso. Tuttavia, la nostra funzionalità è la stessa. Questa volta non abbiamo nemmeno fatto nulla per aggiungere supporto all'editor.
Lingua “Cliente”: Vim
Se non sei ancora convinto che questa separazione delle preoccupazioni renda facile condividere le funzionalità tra gli editor di testo, ecco i passaggi per aggiungere la stessa funzionalità a Vim tramite Coc.
Apri Vim e digita :CocConfig
, quindi aggiungi:
"languageserver": { "blacklister": { "command": "blacklist-server", "args": ["--stdio"], "filetypes": ["text"] } }
Fatto.
La separazione client-server consente alle lingue e ai servizi linguistici di prosperare
Separare la responsabilità dei servizi linguistici dagli editor di testo in cui vengono utilizzati è chiaramente una vittoria. Consente ai creatori di funzionalità linguistiche di concentrarsi sulla loro specialità e ai creatori di editor di fare lo stesso. È un'idea abbastanza nuova, ma l'adozione si sta diffondendo.
Ora che hai una base su cui lavorare, forse puoi trovare un progetto e aiutare a portare avanti questa idea. La guerra delle fiamme dell'editor non finirà mai, ma va bene così. Finché le abilità linguistiche possono esistere al di fuori di editor specifici, sei libero di usare qualsiasi editor tu voglia.
In qualità di Microsoft Gold Partner, Toptal è la tua rete d'élite di esperti Microsoft. Crea team ad alte prestazioni con gli esperti di cui hai bisogno, ovunque ed esattamente quando ne hai bisogno!