Sprachserverprotokoll-Tutorial: Von VSCode zu Vim
Veröffentlicht: 2022-03-11Das wichtigste Artefakt all Ihrer Arbeit sind höchstwahrscheinlich reine Textdateien. Warum verwenden Sie also nicht Notepad, um sie zu erstellen?
Syntaxhervorhebung und automatische Formatierung sind nur die Spitze des Eisbergs. Was ist mit Linting, Codevervollständigung und halbautomatischem Refactoring? All dies sind sehr gute Gründe, einen „echten“ Code-Editor zu verwenden. Diese sind für unseren Alltag von entscheidender Bedeutung, aber verstehen wir, wie sie funktionieren?
In diesem Language Server Protocol-Tutorial gehen wir diesen Fragen ein wenig nach und finden heraus, wie unsere Texteditoren ticken. Am Ende implementieren wir gemeinsam einen einfachen Sprachserver zusammen mit Beispielclients für VSCode, Sublime Text 3 und Vim.
Compiler vs. Sprachdienste
Wir überspringen jetzt die Syntaxhervorhebung und -formatierung, die mit der statischen Analyse behandelt wird – ein interessantes Thema für sich – und konzentrieren uns auf das wichtigste Feedback, das wir von diesen Tools erhalten. Es gibt zwei Hauptkategorien: Compiler und Sprachdienste.
Compiler nehmen Ihren Quellcode auf und spucken ihn in anderer Form aus. Wenn der Code nicht den Regeln der Sprache folgt, gibt der Compiler Fehler zurück. Diese sind ziemlich bekannt. Das Problem dabei ist, dass es normalerweise ziemlich langsam und in seinem Umfang begrenzt ist. Wie wäre es, wenn Sie Hilfe anbieten, während Sie den Code noch erstellen?
Das leisten Sprachdienste. Sie können Ihnen Einblicke in Ihre Codebasis geben, während sie noch in Arbeit ist, und wahrscheinlich viel schneller als das Kompilieren des gesamten Projekts.
Der Umfang dieser Dienstleistungen ist vielfältig. Es kann etwas so Einfaches sein wie das Zurückgeben einer Liste aller Symbole im Projekt oder etwas Komplexes wie das Zurückgeben von Schritten zum Umgestalten von Code. Diese Dienste sind der Hauptgrund, warum wir unsere Code-Editoren verwenden. Wenn wir nur kompilieren und Fehler sehen wollten, könnten wir das mit ein paar Tastendrücken tun. Sprachdienste geben uns mehr Einblicke, und das sehr schnell.
Wetten auf einen Texteditor zum Programmieren
Beachten Sie, dass wir noch keine bestimmten Texteditoren genannt haben. Lassen Sie uns anhand eines Beispiels erklären, warum.
Angenommen, Sie haben eine neue Programmiersprache namens Lapine entwickelt. Es ist eine wunderschöne Sprache und der Compiler gibt tolle Elm-ähnliche Fehlermeldungen aus. Darüber hinaus können Sie Codevervollständigung, Referenzen, Refactoring-Hilfe und Diagnose bereitstellen.
Welchen Code-/Texteditor unterstützen Sie zuerst? Was ist danach? Sie müssen hart kämpfen, um die Leute dazu zu bringen, es zu übernehmen, also möchten Sie es so einfach wie möglich machen. Sie möchten nicht den falschen Editor auswählen und Benutzer verpassen. Was wäre, wenn Sie sich von den Code-Editoren fernhalten und sich auf Ihr Spezialgebiet konzentrieren – die Sprache und ihre Funktionen?
Sprachserver
Sprachserver eingeben . Dies sind Tools, die mit Sprachklienten sprechen und die erwähnten Erkenntnisse liefern. Sie sind aus den Gründen, die wir gerade mit unserer hypothetischen Situation beschrieben haben, unabhängig von Texteditoren.
Wie üblich ist eine weitere Abstraktionsebene genau das, was wir brauchen. Diese versprechen, die enge Kopplung von Sprachwerkzeugen und Code-Editoren zu durchbrechen. Sprachersteller können ihre Funktionen einmal in einen Server packen, und Code-/Texteditoren können kleine Erweiterungen hinzufügen, um sich selbst in Clients zu verwandeln. Es ist ein Gewinn für alle. Um dies zu ermöglichen, müssen wir uns jedoch darauf einigen, wie diese Clients und Server kommunizieren.
Zum Glück für uns ist das nicht hypothetisch. Microsoft hat bereits mit der Definition des Language Server Protocol begonnen.
Wie die meisten großartigen Ideen entstand sie eher aus der Notwendigkeit als aus der Voraussicht. Viele Code-Editoren hatten bereits damit begonnen, Unterstützung für verschiedene Sprachfunktionen hinzuzufügen; Einige Funktionen wurden an Tools von Drittanbietern ausgelagert, andere wurden unter der Haube in den Editoren ausgeführt. Skalierbarkeitsprobleme traten auf, und Microsoft übernahm die Führung bei der Aufteilung von Dingen. Ja, Microsoft hat den Weg geebnet, diese Funktionen aus den Code-Editoren zu entfernen, anstatt sie in VSCode zu horten. Sie hätten ihren Editor weiter bauen und Benutzer sperren können – aber sie haben sie freigelassen.
Sprachserverprotokoll
Das Language Server Protocol (LSP) wurde 2016 definiert, um die Trennung von Sprachtools und Editoren zu unterstützen. Es sind noch viele VSCode-Fingerabdrücke darauf, aber es ist ein großer Schritt in Richtung Editor-Agnostizismus. Lassen Sie uns das Protokoll ein wenig untersuchen.
Clients und Server – denken Sie an Code-Editoren und Sprachwerkzeuge – kommunizieren in einfachen Textnachrichten. Diese Nachrichten haben HTTP-ähnliche Header, JSON-RPC-Inhalte und können entweder vom Client oder vom Server stammen. Das JSON-RPC-Protokoll definiert Anfragen, Antworten und Benachrichtigungen und einige Grundregeln um sie herum. Ein Schlüsselmerkmal ist, dass es asynchron arbeiten soll, sodass Clients/Server Nachrichten außer der Reihe und mit einem gewissen Grad an Parallelität verarbeiten können.
Kurz gesagt, JSON-RPC ermöglicht es einem Client, ein anderes Programm aufzufordern, eine Methode mit Parametern auszuführen und ein Ergebnis oder einen Fehler zurückzugeben. LSP baut darauf auf und definiert die verfügbaren Methoden, die erwarteten Datenstrukturen und einige weitere Regeln rund um die Transaktionen. Beispielsweise gibt es einen Handshake-Prozess, wenn der Client den Server startet.
Der Server ist zustandsbehaftet und nur dazu gedacht, einen einzelnen Client gleichzeitig zu behandeln. Es gibt jedoch keine expliziten Einschränkungen für die Kommunikation, sodass ein Sprachserver auf einer anderen Maschine als der Client laufen könnte . In der Praxis wäre das für Echtzeit-Feedback jedoch ziemlich langsam. Sprachserver und Clients arbeiten mit den gleichen Dateien und sind ziemlich gesprächig.
Der LSP verfügt über eine anständige Menge an Dokumentation, sobald Sie wissen, wonach Sie suchen müssen. Wie bereits erwähnt, wird vieles davon im Kontext von VSCode geschrieben, obwohl die Ideen eine viel breitere Anwendung haben. Beispielsweise ist die Protokollspezifikation vollständig in TypeScript geschrieben. Um Entdeckern zu helfen, die mit VSCode und TypeScript nicht vertraut sind, finden Sie hier eine Einführung.
LSP-Nachrichtentypen
Im Language Server Protocol sind viele Nachrichtengruppen definiert. Sie lassen sich grob in „Admin“- und „Sprachfeatures“ unterteilen. Admin-Nachrichten enthalten diejenigen, die beim Client/Server-Handshake, Öffnen/Ändern von Dateien usw. verwendet werden. Wichtig ist, dass Clients und Server hier die Funktionen teilen, die sie handhaben. Sicherlich bieten unterschiedliche Sprachen und Tools unterschiedliche Funktionen. Dies ermöglicht auch eine schrittweise Übernahme. Langserver.org nennt ein halbes Dutzend Schlüsselfunktionen, die Clients und Server unterstützen sollten, von denen mindestens eine erforderlich ist, um die Liste zu erstellen.
Sprachmerkmale sind das, was uns am meisten interessiert. Von diesen gibt es eine, die besonders hervorzuheben ist: die Diagnosemeldung. Diagnose ist eines der Hauptmerkmale. Wenn Sie eine Datei öffnen, wird meistens davon ausgegangen, dass diese ausgeführt wird. Ihr Lektor sollte Ihnen mitteilen, ob mit der Datei etwas nicht stimmt. So geschieht dies mit LSP:
- Der Client öffnet die Datei und sendet
textDocument/didOpen
an den Server. - Der Server analysiert die Datei und sendet die Benachrichtigung
textDocument/publishDiagnostics
. - Der Client analysiert die Ergebnisse und zeigt Fehlerindikatoren im Editor an.
Dies ist eine passive Methode, um Einblicke in Ihre Sprachdienste zu erhalten. Ein aktiveres Beispiel wäre, alle Referenzen für das Symbol unter Ihrem Cursor zu finden. Das würde etwa so gehen:
- Der Client sendet
textDocument/references
an den Server und gibt einen Speicherort in einer Datei an. - Der Server ermittelt das Symbol, findet Verweise in dieser und anderen Dateien und antwortet mit einer Liste.
- Der Client zeigt dem Benutzer die Referenzen an.
Ein Blacklist-Tool
Wir könnten sicherlich in die Besonderheiten des Language Server Protocol eintauchen, aber überlassen wir das den Client-Implementierern. Um die Idee der Trennung von Editor und Sprachwerkzeug zu festigen, übernehmen wir die Rolle des Werkzeugerstellers.
Wir werden es einfach halten und, anstatt eine neue Sprache und neue Funktionen zu erstellen, bei der Diagnose bleiben. Diagnosen passen gut: Sie sind nur Warnungen zum Inhalt einer Datei. Ein Linter gibt Diagnosen zurück. Wir werden etwas Ähnliches machen.
Wir werden ein Tool erstellen, um uns über Wörter zu informieren, die wir vermeiden möchten. Anschließend stellen wir diese Funktionalität einigen verschiedenen Texteditoren zur Verfügung.
Der Sprachserver
Zuerst das Werkzeug. Wir backen das direkt in einen Sprachserver ein. Der Einfachheit halber wird dies eine Node.js-App sein, obwohl wir dies mit jeder Technologie tun könnten, die in der Lage ist, Streams zum Lesen und Schreiben zu verwenden.
Hier ist die Logik. Bei gegebenem Text gibt diese Methode ein Array der übereinstimmenden Wörter auf der schwarzen Liste und die Indizes zurück, in denen sie gefunden wurden.
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 }
Machen wir es jetzt zu einem 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()
Hier verwenden wir den vscode-languageserver
. Der Name ist irreführend, da es sicherlich auch außerhalb von VSCode funktionieren kann. Dies ist einer der vielen „Fingerabdrücke“ der Ursprünge von LSP. vscode-languageserver
kümmert sich um das untergeordnete Protokoll und ermöglicht es Ihnen, sich auf die Anwendungsfälle zu konzentrieren. Dieses Snippet startet eine Verbindung und bindet sie an einen Dokumentenmanager. Wenn sich ein Client mit dem Server verbindet, teilt ihm der Server mit, dass er benachrichtigt werden möchte, wenn Textdokumente geöffnet werden.

Wir könnten hier aufhören. Dies ist ein voll funktionsfähiger, wenn auch sinnloser LSP-Server. Lassen Sie uns stattdessen mit einigen Diagnoseinformationen auf Dokumentänderungen reagieren.
documents.onDidChangeContent(change => { connection.sendDiagnostics({ uri: change.document.uri, diagnostics: getDiagnostics(change.document), }) })
Schließlich verbinden wir die Punkte zwischen dem geänderten Dokument, unserer Logik und der Diagnoseantwort.
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', })
Unsere Diagnose-Payload ist das Ergebnis, wenn der Text des Dokuments durch unsere Funktion läuft und dann dem vom Client erwarteten Format zugeordnet wird.
Dieses Skript erstellt all das für Sie.
curl -o- https://raw.githubusercontent.com/reergymerej/lsp-article-resources/revision-for-6.0.0/blacklist-server-install.sh | bash
Hinweis: Wenn es Ihnen unangenehm ist, wenn Fremde ausführbare Dateien zu Ihrem Computer hinzufügen, überprüfen Sie bitte die Quelle. Es erstellt das Projekt, lädt index.js
herunter und npm link
verlinkt es für Sie.
Vollständige Serverquelle
Die letzte blacklist-server
Quelle ist:
#!/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()
Language Server Protocol Tutorial: Zeit für eine Probefahrt
Nachdem das Projekt link
ist, versuchen Sie, den Server auszuführen, und stdio
als Transportmechanismus an:
blacklist-server --stdio
Es hört jetzt auf stdio
auf die LSP-Nachrichten, über die wir zuvor gesprochen haben. Wir könnten diese manuell bereitstellen, aber erstellen wir stattdessen einen Client.
Sprachclient: VSCode
Da diese Technologie ihren Ursprung in VSCode hat, scheint es angebracht, dort anzusetzen. Wir erstellen eine Erweiterung, die einen LSP-Client erstellt und ihn mit dem gerade erstellten Server verbindet.
Es gibt eine Reihe von Möglichkeiten, eine VSCode-Erweiterung zu erstellen, einschließlich der Verwendung von Yeoman und dem entsprechenden Generator, generator-code
. Machen wir der Einfachheit halber ein Barebone-Beispiel.
Lassen Sie uns die Boilerplate klonen und ihre Abhängigkeiten installieren:
git clone [email protected]:reergymerej/standalone-vscode-ext.git blacklist-vscode cd blacklist-vscode npm i # or yarn
Öffnen Sie das Verzeichnis blacklist-vscode
in VSCode.
Drücken Sie F5, um eine weitere VSCode-Instanz zu starten und die Erweiterung zu debuggen.
In der „Debug Console“ der ersten VSCode-Instanz sehen Sie den Text „Look, ma. Eine Erweiterung!"
Wir haben jetzt eine grundlegende VSCode-Erweiterung, die ohne den ganzen Schnickschnack funktioniert. Machen wir es zu einem LSP-Client. Schließen Sie beide VSCode-Instanzen und führen Sie im Verzeichnis blacklist-vscode
aus:
npm i vscode-languageclient
Ersetzen Sie extension.js durch:
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()) }, }
Dies verwendet das Paket vscode-languageclient
, um einen LSP-Client in VSCode zu erstellen. Im Gegensatz zu vscode-languageserver
ist dies eng mit VSCode gekoppelt. Kurz gesagt, wir erstellen in dieser Erweiterung einen Client und weisen ihn an, den Server zu verwenden, den wir in den vorherigen Schritten erstellt haben. Wenn wir die Besonderheiten der VSCode-Erweiterung beschönigen, können wir sehen, dass wir sagen, dass wir diesen LSP-Client für reine Textdateien verwenden sollen.
Um es zu testen, öffnen Sie das Verzeichnis blacklist-vscode
in VSCode. Drücken Sie F5, um eine weitere Instanz zu starten und die Erweiterung zu debuggen.
Erstellen Sie in der neuen VSCode-Instanz eine Nur-Text-Datei und speichern Sie sie. Geben Sie „foo“ oder „bar“ ein und warten Sie einen Moment. Sie werden Warnungen sehen, dass diese auf der schwarzen Liste stehen.
Das ist es! Wir mussten unsere Logik nicht neu erstellen, sondern nur Client und Server koordinieren.
Machen wir es noch einmal für einen anderen Editor, diesmal Sublime Text 3. Der Prozess wird ziemlich ähnlich und etwas einfacher sein.
Sprachclient: Sublime Text 3
Öffnen Sie zuerst ST3 und öffnen Sie die Befehlspalette. Wir brauchen ein Framework, um den Editor zu einem LSP-Client zu machen. Geben Sie „Package Control: Install Package“ ein und drücken Sie die Eingabetaste. Suchen Sie das Paket „LSP“ und installieren Sie es. Nach Abschluss haben wir die Möglichkeit , LSP-Clients anzugeben. Es gibt viele Voreinstellungen, aber wir werden diese nicht verwenden. Wir haben unsere eigenen erstellt.
Öffnen Sie erneut die Befehlspalette. Suchen Sie „Einstellungen: LSP-Einstellungen“ und drücken Sie die Eingabetaste. Dadurch wird die Konfigurationsdatei LSP.sublime-settings
für das LSP-Paket geöffnet. Um einen benutzerdefinierten Client hinzuzufügen, verwenden Sie die nachstehende Konfiguration.
{ "clients": { "blacklister": { "command": [ "blacklist-server", "--stdio" ], "enabled": true, "languages": [ { "syntaxes": [ "Plain text" ] } ] } }, "log_debug": true }
Dies kann Ihnen von der VSCode-Erweiterung bekannt vorkommen. Wir haben einen Client definiert, ihn angewiesen, mit Klartextdateien zu arbeiten, und den Sprachserver angegeben.
Speichern Sie die Einstellungen, erstellen und speichern Sie dann eine Nur-Text-Datei. Geben Sie „foo“ oder „bar“ ein und warten Sie. Auch hier sehen Sie Warnungen, dass diese auf der schwarzen Liste stehen. Die Behandlung – wie die Nachrichten im Editor angezeigt werden – ist anders. Unsere Funktionalität ist jedoch die gleiche. Wir haben dieses Mal kaum etwas unternommen, um den Editor zu unterstützen.
Sprache „Kunde“: Vim
Wenn Sie immer noch nicht davon überzeugt sind, dass diese Trennung von Bedenken es einfach macht, Funktionen über Texteditoren hinweg zu teilen, finden Sie hier die Schritte, um Vim über Coc dieselbe Funktionalität hinzuzufügen.
Öffnen Sie Vim und :CocConfig
, fügen Sie dann hinzu:
"languageserver": { "blacklister": { "command": "blacklist-server", "args": ["--stdio"], "filetypes": ["text"] } }
Getan.
Client-Server-Trennung lässt Sprachen und Sprachdienste gedeihen
Die Trennung der Verantwortung der Sprachdienste von den Texteditoren, in denen sie verwendet werden, ist eindeutig ein Gewinn. Es ermöglicht den Erstellern von Sprachfeatures, sich auf ihr Spezialgebiet zu konzentrieren, und den Erstellern von Editoren, dasselbe zu tun. Es ist eine ziemlich neue Idee, aber die Akzeptanz breitet sich aus.
Jetzt, da Sie eine Grundlage haben, auf der Sie arbeiten können, können Sie vielleicht ein Projekt finden und helfen, diese Idee voranzutreiben. Der Flammenkrieg des Editors wird nie enden, aber das ist in Ordnung. Solange die Sprachfähigkeiten außerhalb bestimmter Editoren vorhanden sind, steht es Ihnen frei, jeden beliebigen Editor zu verwenden.
Als Microsoft Gold Partner ist Toptal Ihr Elite-Netzwerk von Microsoft-Experten. Bauen Sie leistungsstarke Teams mit den Experten auf, die Sie brauchen – überall und genau dann, wenn Sie sie brauchen!