ist jetzt verfügbar! Lesen Sie über die neuen Funktionen und Fehlerbehebungen vom November.

Eingebettete Programmiersprachen

Visual Studio Code bietet umfassende Sprachfunktionen für Programmiersprachen. Wie Sie im Leitfaden für Language Server-Erweiterungen (Language Server extension guide) gelesen haben, können Sie Language Server schreiben, um jede Programmiersprache zu unterstützen. Es erfordert jedoch mehr Aufwand, um eine solche Unterstützung für eingebettete Sprachen zu ermöglichen.

Heute gibt es eine wachsende Zahl von eingebetteten Sprachen, wie zum Beispiel

  • JavaScript und CSS in HTML
  • JSX in JavaScript
  • Interpolation in Template-Sprachen, zum Beispiel Vue, Handlebars und Razor
  • HTML in PHP

Dieser Leitfaden konzentriert sich auf die Implementierung von Sprachfunktionen für eingebettete Sprachen. Wenn Sie daran interessiert sind, Syntaxhervorhebungen für eingebettete Sprachen bereitzustellen, finden Sie Informationen im Leitfaden zur Syntaxhervorhebung (Syntax Highlight guide).

Dieser Leitfaden enthält zwei Beispiele, die zwei Ansätze zur Erstellung eines solchen Language Servers veranschaulichen: Language Services und Request Forwarding. Wir werden beide Beispiele überprüfen und mit den Vor- und Nachteilen jedes Ansatzes abschließen.

Der Quellcode für beide Beispiele finden Sie unter

Hier ist der eingebettete Sprachserver, den wir erstellen werden

sample

Beide Beispiele stellen zu Illustrationszwecken eine neue Sprache, html1, bereit. Sie können eine Datei .html1 erstellen und die folgenden Funktionalitäten testen

  • Vervollständigungen für HTML-Tags
  • Vervollständigungen für CSS im <style>-Tag
  • Diagnosen für CSS (nur im Language Services-Beispiel)

Language Services

Ein Language Service ist eine Bibliothek, die programmatische Sprachfunktionen für eine einzelne Sprache implementiert. Ein Language Server kann Language Services einbetten, um eingebettete Sprachen zu verarbeiten.

Hier ist eine Übersicht über die HTML-Unterstützung von VS Code

Der HTML Language Server analysiert ein HTML-Dokument, zerlegt es in Sprachregionen und verwendet den entsprechenden Language Service, um Anfragen des Language Servers zu bearbeiten.

Zum Beispiel

  • Für eine Auto-Vervollständigungsanfrage an <| verwendet der HTML Language Server den HTML Language Service, um HTML-Vervollständigungen bereitzustellen.
  • Für eine Auto-Vervollständigungsanfrage an <style>.foo { | }</style> verwendet der HTML Language Server den CSS Language Service, um CSS-Vervollständigungen bereitzustellen.

Sehen wir uns das Beispiel lsp-embedded-language-service an, eine vereinfachte Version des HTML Language Servers, der Auto-Vervollständigung für HTML und CSS sowie Fehlerdiagnosen für CSS implementiert.

Language Services Beispiel

Hinweis: Dieses Beispiel setzt Kenntnisse des Themas Programmatic Language Features und des Leitfadens für Language Server-Erweiterungen (Language Server extension guide) voraus. Der Code baut auf dem lsp-sample auf.

Der Quellcode ist unter microsoft/vscode-extension-samples verfügbar.

Im Vergleich zum lsp-sample ist der clientseitige Code derselbe.

Wie oben erwähnt, zerlegt der Server das Dokument in verschiedene Sprachregionen, um die eingebetteten Inhalte zu verarbeiten.

Hier ist ein einfaches Beispiel

<div></div>
<style>.foo { }</style>

In diesem Fall erkennt der Server das <style>-Tag und markiert .foo { } als CSS-Region.

Bei einer Auto-Vervollständigungsanfrage an einer bestimmten Position verwendet der Server die folgende Logik, um eine Antwort zu berechnen

  • Wenn die Position in eine Region fällt
    • Behandeln Sie sie mit einem virtuellen Dokument mit der Sprache der Region, wobei alle anderen Regionen durch Leerzeichen ersetzt werden
  • Wenn die Position außerhalb einer Region liegt
    • Behandeln Sie sie mit einem virtuellen Dokument in HTML, wobei alle Regionen durch Leerzeichen ersetzt werden

Wenn Sie beispielsweise eine Auto-Vervollständigung an dieser Position durchführen

<div></div>
<style>.foo { | }</style>

Der Server ermittelt, dass sich die Position innerhalb der Region befindet, und berechnet ein virtuelles CSS-Dokument mit folgendem Inhalt (█ steht für Leerzeichen)

███████████
███████.foo { | }████████

Der Server verwendet dann vscode-css-languageservice, um dieses Dokument zu analysieren und eine Liste von Vervollständigungselementen zu erstellen. Da der Inhalt nun kein HTML mehr enthält, kann der CSS Language Service ihn problemlos verarbeiten. Durch das Ersetzen aller Nicht-CSS-Inhalte durch Leerzeichen müssen wir die Positionen nicht manuell versetzen.

Der Servercode, der Vervollständigungsanfragen bearbeitet

connection.onCompletion(async (textDocumentPosition, token) => {
  const document = documents.get(textDocumentPosition.textDocument.uri);
  if (!document) {
    return null;
  }

  const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
  if (!mode || !mode.doComplete) {
    return CompletionList.create();
  }
  const doComplete = mode.doComplete!;

  return doComplete(document, textDocumentPosition.position);
});

Der CSS-Modus, der für die Bearbeitung aller Language Server-Anfragen zuständig ist, die in CSS-Regionen fallen

export function getCSSMode(
  cssLanguageService: CSSLanguageService,
  documentRegions: LanguageModelCache<HTMLDocumentRegions>
): LanguageMode {
  return {
    getId() {
      return 'css';
    },
    doComplete(document: TextDocument, position: Position) {
      // Get virtual CSS document, with all non-CSS code replaced with whitespace
      const embedded = documentRegions.get(document).getEmbeddedDocument('css');
      // Compute a response with vscode-css-languageservice
      const stylesheet = cssLanguageService.parseStylesheet(embedded);
      return cssLanguageService.doComplete(embedded, position, stylesheet);
    }
  };
}

Dies ist ein einfacher und effektiver Ansatz zur Behandlung eingebetteter Sprachen. Dieser Ansatz hat jedoch einige Nachteile

  • Sie müssen die Language Services, von denen Ihr Language Server abhängt, kontinuierlich aktualisieren.
  • Es kann schwierig sein, Language Services einzubinden, die nicht in derselben Sprache wie Ihr Language Server geschrieben sind. Zum Beispiel würde ein PHP Language Server, der in PHP geschrieben ist, es als umständlich empfinden, vscode-css-languageservice einzubinden, das in TypeScript geschrieben ist.

Wir werden nun Request Forwarding behandeln, das die oben genannten Probleme lösen würde.

Request Forwarding

Kurz gesagt, Request Forwarding funktioniert ähnlich wie Language Services. Der Request Forwarding-Ansatz nimmt ebenfalls Language Server-Anfragen entgegen, berechnet virtuelle Inhalte und berechnet die Antworten.

Die Hauptunterschiede sind

  • Während der Language Service-Ansatz Bibliotheken zur Berechnung von Language Server-Antworten verwendet, sendet Request Forwarding die Anfrage zurück an VS Code, um aktive Erweiterungen zu verwenden, die einen Vervollständigungsanbieter für die eingebettete Sprache registriert haben.

Hier ist wieder das einfache Beispiel

<div></div>
<style>.foo { | }</style>

Die Auto-Vervollständigung erfolgt auf diese Weise

  • Der Language Client registriert einen virtuellen Textdokumentanbieter für das Dokument embedded-content über workspace.registerTextDocumentContentProvider.
  • Der Language Client fängt Vervollständigungsanfragen für <FILE_URI> ab.
  • Der Language Client stellt fest, dass die Anfrageposition in eine CSS-Region fällt.
  • Der Language Client konstruiert eine neue URI, z. B. embedded-content://css/<FILE_URI>.css.
  • Der Language Client ruft dann commands.executeCommand('vscode.executeCompletionItemProvider', ...) auf.
    • Der CSS Language Server von VS Code reagiert auf diese Anbieteranfrage.
    • Der virtuelle Textdokumentanbieter stellt dem CSS Language Server virtuelle Inhalte zur Verfügung, bei denen aller Nicht-CSS-Code durch Leerzeichen ersetzt wird.
    • Der Language Client erhält die Antwort von VS Code und sendet sie als Antwort.

Mit diesem Ansatz können wir CSS-Auto-Vervollständigungen berechnen, auch wenn unser Code keine Bibliothek enthält, die CSS versteht. Da VS Code seinen CSS Language Server aktualisiert, erhalten wir die neueste CSS-Sprachunterstützung, ohne unseren Code aktualisieren zu müssen.

Sehen wir uns nun den Beispielcode an.

Request Forwarding Beispiel

Hinweis: Dieses Beispiel setzt Kenntnisse des Themas Programmatic Language Features und des Leitfadens für Language Server-Erweiterungen (Language Server extension guide) voraus. Der Code baut auf dem lsp-sample auf.

Der Quellcode ist unter microsoft/vscode-extension-samples verfügbar.

Führen einer Zuordnung zwischen den URIs von Dokumenten und ihren virtuellen Dokumenten und deren Bereitstellung für entsprechende Anfragen

const virtualDocumentContents = new Map<string, string>();

workspace.registerTextDocumentContentProvider('embedded-content', {
  provideTextDocumentContent: uri => {
    // Remove leading `/` and ending `.css` to get original URI
    const originalUri = uri.path.slice(1).slice(0, -4);
    const decodedUri = decodeURIComponent(originalUri);
    return virtualDocumentContents.get(decodedUri);
  }
});

Durch die Verwendung der middleware-Option des Language Clients fangen wir Anfragen zur Auto-Vervollständigung ab

let clientOptions: LanguageClientOptions = {
  documentSelector: [{ scheme: 'file', language: 'html' }],
  middleware: {
    provideCompletionItem: async (document, position, context, token, next) => {
      // If not in `<style>`, do not perform request forwarding
      if (
        !isInsideStyleRegion(
          htmlLanguageService,
          document.getText(),
          document.offsetAt(position)
        )
      ) {
        return await next(document, position, context, token);
      }

      const originalUri = document.uri.toString(true);
      virtualDocumentContents.set(
        originalUri,
        getCSSVirtualContent(htmlLanguageService, document.getText())
      );

      const vdocUriString = `embedded-content://css/${encodeURIComponent(originalUri)}.css`;
      const vdocUri = Uri.parse(vdocUriString);
      return await commands.executeCommand<CompletionList>(
        'vscode.executeCompletionItemProvider',
        vdocUri,
        position,
        context.triggerCharacter
      );
    }
  }
};

Potenzielle Probleme

Bei der Implementierung von eingebetteten Sprachservern sind wir auf viele Probleme gestoßen. Obwohl wir noch keine perfekte Lösung haben, möchten wir Sie darauf hinweisen, dass Sie wahrscheinlich auch auf diese Probleme stoßen werden.

Schwierig zu implementierende Sprachfunktionen

Im Allgemeinen sind Sprachfunktionen, die sprachregionsübergreifend funktionieren, schwieriger zu implementieren. Zum Beispiel sind Auto-Vervollständigung oder Hover-Inhalte einfach zu implementieren, da Sie die Sprache des eingebetteten Inhalts erkennen und eine Antwort basierend auf dem eingebetteten Inhalt berechnen können. Sprachfunktionen wie Formatierung oder Umbenennung erfordern jedoch möglicherweise eine spezielle Behandlung. Bei der Formatierung müssen Sie Einrückungen und Formatierereinstellungen für mehrere Regionen innerhalb desselben Dokuments handhaben. Bei der Umbenennung kann es schwierig sein, sie für verschiedene Regionen innerhalb verschiedener Dokumente funktionierend zu machen.

Language Services können zustandsbehaftet und schwer einzubetten sein

Die HTML-Unterstützung von VS Code bietet HTML-, CSS- und JavaScript-Sprachfunktionen. Obwohl die HTML- und CSS-Language Services zustandslos sind, ist der TypeScript-Server, der die JavaScript-Sprachfunktionen unterstützt, zustandsbehaftet. Wir bieten nur grundlegende JavaScript-Unterstützung innerhalb von HTML-Dokumenten, da es schwierig ist, TypeScript über den Zustand des Projekts zu informieren. Wenn Sie beispielsweise ein <script>-Tag einfügen, das auf die lodash-Bibliothek verweist, die auf einem CDN gehostet wird, erhalten Sie keine _.-Vervollständigungen innerhalb von <script>-Tags.

Kodierung und Dekodierung

Die Hauptsprache eines Dokuments kann eine andere Kodierung oder Escape-Regeln haben als seine eingebettete Sprache. Zum Beispiel ist dieses HTML-Dokument gemäß der HTML-Spezifikation ungültig.

<SCRIPT type="text/javascript">
  document.write ("<EM>This won't work</EM>")
</SCRIPT>

In diesem Fall, wenn der Language Server für das eingebettete JavaScript ein Ergebnis zurückgibt, das </ enthält, sollte es als <\/ escaped werden.

Fazit

Beide Ansätze haben ihre Vor- und Nachteile.

Language Service

  • + Volle Kontrolle über den Language Server und die Benutzererfahrung.
  • + Keine Abhängigkeiten von anderen Language Servern. Der gesamte Code befindet sich in einem Repository.
  • + Der Language Server kann in allen LSP-konformen Code-Editoren wiederverwendet werden.
  • - Kann schwierig sein, Language Services einzubetten, die in anderen Sprachen geschrieben sind.
  • - Benötigt kontinuierliche Wartung, um neue Funktionen von Language Service-Abhängigkeiten zu erhalten.

Request Forwarding

  • + Vermeidet Probleme beim Einbetten von Language Services, die nicht in der Sprache des Language Servers geschrieben sind (z. B. Einbetten eines C#-Compilers in einen Razor-Language-Server zur Unterstützung von C#).
  • + Keine Wartung erforderlich, um neue Funktionen von anderen Language Services upstream zu erhalten.
  • - Funktioniert nicht mit Diagnosefehlern. Die VS Code API unterstützt keine Diagnoseanbieter, die Diagnosen "ziehen" (anfordern) können.
  • - Schwierig, Zustand an andere Language Server zu übergeben, aufgrund mangelnder Kontrolle.
  • - Sprachübergreifende Funktionen können schwierig zu implementieren sein (z. B. Bereitstellung von CSS-Vervollständigungen für .foo, wenn <div class="foo"> vorhanden ist).

Insgesamt empfehlen wir, einen Language Server durch Einbetten von Language Services zu erstellen, da dieser Ansatz Ihnen mehr Kontrolle über die Benutzererfahrung gibt und der Server für jeden LSP-konformen Editor wiederverwendbar ist. Wenn Sie jedoch einen einfachen Anwendungsfall haben, bei dem eingebettete Inhalte ohne Kontext oder Language Server-Zustand leicht behandelt werden können, oder wenn das Bündeln der Node.js-Bibliothek für Sie ein Problem darstellt, können Sie den Request Forwarding-Ansatz in Betracht ziehen.

© . This site is unofficial and not affiliated with Microsoft.