Sprachlehrer für Visual Studio Code

Für Microsofts Sourcecode-Editor lassen sich ohne viel Aufwand Erweiterungen erstellen, die beim Entwickeln mit weniger verbreiteten Programmiersprachen helfen.

Lesezeit: 8 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 12 Beiträge
Von

Inhaltsverzeichnis

Hilfen wie Syntax-Hervorhebung, Autovervollständigung und gute Code-Schnipsel sind für viele Entwickler das A und O einer Entwicklungsumgebung. Visual Studio Code bietet nicht nur viele Erweiterungen für zahlreiche Programmiersprachen, sondern ermöglicht darüber hinaus das Erstellen eigener Extensions, wenn die Programmiersprache der Wahl nicht auf der Liste steht. Der Artikel zeigt am Beispiel der Template-Sprache von FirstSpirit, wie Entwickler eine einfache eigene Erweiterung für die IDE erstellen können.

Das Produkt FirstSpirit ist ein Enterprise-CMS der Firma e-Spirit. Damit können Redakteure Inhalte nicht nur in Standard-Eingabekomponenten erfassen, sondern Entwickler können zusätzlich spezielle Eingabekomponenten erstellen. Die Anwendung gibt die von den Redakteuren erfassten Informationen anhand von Vorlagen aus, für deren Erstellung FirstSpirit über eine eigene Template-Sprache verfügt.

Zum Erstellen einer Erweiterung für Visual Studio Code sieht die Dokumentation die Werkzeuge npm, Yeoman und Visual Studio Code Extension Generator vor. Nach der Installation von npm installiert folgende Zeile Yeoman und den Generator auf dem System:

npm install -g yo generator-code
yo code

Der Extension Generator erstellt das Grundgerüst in einer passenden Dateistruktur, die er mit einem einfachen Beispiel befüllt. Dazu fragt er ein paar Punkte ab (s. Abb. 1).

Erstellen eines Grundgerüstes mit Yeoman und Visual Studio Code Extension Generator (Abb. 1).

Alle im Artikel gezeigten Beispiele sind unter Windows 10 und Ubuntu getestet und sollten sich auf jedem anderen aktuellen Linux sowie unter macOS ebenso durchführen lassen.

Unter Linux und macOS installieren die Befehle npm install -g ... die genannten Komponenten systemweit mit dem Parameter -g und gegebenenfalls einem vorgestellten sudo für administrative Rechte. Auf Windows Systemen muss das Kommandofenster administrative Rechte erhalten.

Das mit dem Generator erstellte Paket enthält drei wichtige Dateien: package.json mit den Grundeinstellungen des Projekts, language-configuration.json mit der eigentlichen Konfiguration der Sprache und fs.tmLanguage.json im Unterordner syntaxes mit der Syntax der Sprache im TextMate-Format.

package.json enthält neben den Grundeinstellungen des Projekts unter dem Punkt contributes die Elemente, die die Erweiterung zu Visual Studio Code beisteuert (s. Abb. 2).

package.json beschreibt die Grundeinstellungen und Inhalte der Erweiterung (Abb. 2).

Der erste Punkt unter contributes ist languages, der die Programmiersprache(n) definiert. Den in id definierten Wert referenzieren die folgenden Elemente. configuration verweist auf die Konfiguration der angegebenen Sprache – im konkreten Fall auf die Datei language-configuration.json.

Der Punkt grammars definiert die Grammatik der Sprache. Dessen Unterpunkt language gibt die id der Sprache an, für die die Grammatik gilt. path verweist auf die Datei mit der Definition der Grammatik.

Die Datei language-configuration.json enthält die Konfiguration der Sprache in folgenden Abschnitten:

  • comments bestimmt das Format der Kommentare, unterteilt in lineComment blockComment für einzeilige beziehungsweise Blockkommentare. Die Angaben verwendet Visual Studio Code für die Standardfunktion zum Ein- und Auskommentieren wie Shift | Alt | A zum Umschalten von Blockkommentaren. Da FirstSpirit keine Zeilenkommentare kennt, fehlt die Angabe im Beispiel. Blockkommentare starten mit $-- und enden auf --$.
  • brackets beschreiben die gültigen Klammerpaare. Der Editor verwendet die Angabe unter anderem, um das passende Gegenstück zu einer selektierten Klammer hervorzuheben.
  • autoClosingPairs definiert Kombinationen, bei denen Visual Studio Code den zweiten Teil automatisch einfügt, nachdem Entwickler den ersten Teil getippt haben.
  • Die Angabe unter surroundingPairs beschreibt Klammerpaare, die einen gewählten Bereich einschließen. Wenn beispielsweise ( und ) als Paar angegeben sind, können Entwickler einen beliebigen Text markieren und "(" eingeben, damit der Editor den Bereich mit dem gewählten Klammerpaar einschließt.

Die language-configuration.json sieht für die FirstSpirit-Definition folgendermaßen aus:

{
  "comments": {
    "blockComment": [ "$--", "--$" ]
  },
  "brackets": [
    ["{", "}"], ["[", "]"], ["(", ")"]
  ],
  "autoClosingPairs": [
    ["{", "}"],
    ["[", "]"],
    ["(", ")"],
    ["\"", "\""],
    ["'", "'"],
    { "open": "$--", "close": "--$", 
      "notIn": ["comment"] },
    ],
  "surroundingPairs": [
    ["{", "}"],
    ["[", "]"],
    ["(", ")"],
    ["\"", "\""],
    ["'", "'"]
  ]
}

Die Definition von Syntaxhervorhebungen in Form der farblichen Markierungen für einzelne Befehle und Schlüsselworte erfolgen in einer Erweiterung für Visual Studio Code in zwei Teilen:

  • dem Zerlegen des Editor-Inhalts in Tokens, also Worte oder Abschnitte, die eine bestimmte Bedeutung haben und
  • der Farbgebung.

Beides geschieht in der Datei fs.tmLanguage.json. Der Inhalt der Datei ist im TextMate-Format angelegt: ein JSON-Format, mit dem sich reguläre Ausdrücke (nach Oniguruma) verwenden lassen, um den Text in Tokens zu zerlegen und ihnen Namen beziehungsweise Scopes zuzuweisen. Dabei handelt es sich um die Einfärbung (ähnlich wie CSS-Klassen), die einem Farbschema entsprechen müssen.

Um der Grammatik der Sprache nicht zusätzlich eine eigene Farbgebung hinzuzufügen und den Nutzern der Erweiterung eigene Farben aufzudrängen, ist es sinnvoll, sich an den existierenden Namen beziehungsweise Scopes zu orientieren, die sich unter anderem in den Theme-Dateien wie light_plus.json für Visual Studio Code finden.

Eine einfache Regel sieht folgendermaßen aus:

"closetag": {
  "name": "keyword.control.fs",
  "match": "\\$CMS_END_([^\\$]+)\\$",
  "captures": {
    "1": {
      "name": "entity.name.class.fs"
    }
  }
}

  • Das Schlüsselwort der Regel (hier closetag) kann beliebig ausfallen, muss aber innerhalb der Datei eindeutig sein.
  • name bezeichnet den Scope und damit die Färbung des erkannten Tokens. Es stellt eine Hierarchie dar und muss laut Konvention in der ID der Sprache (fs) enden. keyword.control bezeichnet die Standardfarbe für Schlüsselworte, die den Programmablauf steuern (im konkreten Beispiel lila).
  • match bestimmt den regulären Ausdruck zum Erkennen des Tokens: Im konkreten Fall alle Bereiche in der Form $CMS_END_Kommando$, wobei der Teil Kommando – alles nach dem zweiten Unterstrich und vor dem zweiten Dollar-Zeichen – zusätzlich in eine sogenannte Capture Group gefasst ist, die an den runden Klammern erkennbar ist.
  • Mit capture lassen sich die Capture Groups des regulären Ausdrucks mit abweichenden Namen beziehungsweise Scopes versehen. Capture Groups beginnen in der Zählung immer bei 0, wobei die 0-Gruppe den ganzen erkannten Ausdruck darstellt. Im konkreten Fall wird einzig die 1-Gruppe mit entity.name.class.fs benannt. Der erste Teil, entity.name.class, bezeichnet die Standardfarbe für eine Klasse, die im konkreten Beispiel grün ist. fs ist erneut die ID der aktuellen Sprache.

Alternativ können Entwickler zwei reguläre Ausdrücke angeben: begin beschreibt den Anfang und end das Ende des Tokens. Falls in dieser Syntax Capture Groups vorkommen sollen, müsste entsprechend beginCaptures und endCaptures das captures ersetzen:

"comment": {
"name": "comment.line.fs",
"begin": "\\$--",
"end": "--\\$"
}

Bei Codeschnipseln handelt es sich um vorbereitete Fragmente, die Entwickler über eine Abkürzung in den bestehenden Code einfügen können. Die Definition erfolgt unter snippets im Abschnitt contributes in der Datei package.json:

"snippets": [{
"language": "fs",
"path": "./snippets/fs.json"
}]

Erneut beschreibt language die ID der Programmiersprache und path verweist auf die Datei, die die Schnipsel beschreibt. Entwickler müssen sie manuell anlegen, um darin die Definition der Schnipsel im TextMate-Format zu deklarieren. Folgender Abschnitt definiert ein $CMS_FOR()-Codeschnipsel.

"cms_for": {
  "prefix": "cms-for",
  "body": [
    "\\$CMS_FOR(${1:identifier}, ${2:object})\\$",
    "$0",
    "\\$CMS_END_FOR\\$"
  ],
  "description": "For Loop"
}

Der Code fügt drei Zeilen mit zwei Platzhaltern ein und platziert anschließend den Cursor in der zweiten Zeile zwischen $CMS_FOR()$ und $CMS_END_FOR$ (s. Abb. 3).

Der Schlüssel für den Schnipsel (cms_for) ist beliebig wählbar, muss aber in der Datei eindeutig sein. description gibt einen Text an, den Visual Studio Code als Beschreibung bei der Auswahl des Schnipsels darstellt. prefix definiert das Kürzel, das das Einfügen im Editor auslöst. body gibt den Inhalt als Array an: Jede Zeile des Codeschnipsels entspricht einem Element im Array.

Der Inhalt des Schnipsels kann auf einige Variablen wie den Namen der aktuell im Editor geöffneten Datei zugreifen. Eine vollständige Liste der Variablen ist in der Dokumentation verfügbar. Zusätzlich lassen sich Tabulator-Sprungpunkte für Platzhalter definieren, die nach dem Einfügen des Code-Schnipsels noch ausgefüllt werden müssen.

Der spezielle Sprungpunkt $0 bestimmt die Position des Cursors, nach dem Einfügen des Schnipsels und Auffüllen aller Platzhalter. Standardmäßig steht der Cursor hinter dem letzten Zeichen des Codeschnipsels.

Einfügen des $CMS_FOR()$-Code-Schnipsels (Abb. 3)

Code-Folding beschreibt das Zu- und Aufklappen bestimmter Bereiche im Editor, um den Inhalt für die besseren Übersicht auszublenden. Visual Studio Code bietet einen integrierten Mechanismus, für den es die Einrückungstiefe des Quellcodes nutzt. Diese Form des Ausblendens funktioniert immer und ist nicht abschaltbar.

Eine zusätzliche Regel lässt sich in der Sprachkonfiguration erstellen. Entwickler können in der language-configuration.json unter folding mit regulären Ausdrücken für die Start- und die Endzeile eines Bereichs eine zusätzliche Regel für das Code-Folding einrichten:

"folding": {
  "markers": {
    "start": "^.*\\$CMS_(?!END)[^\\$]+\\$.*$",
    "end": "^.*\\$CMS_END_[^\\$]+\\$.*$"
  }
}

Der Nachteil ist, dass sich auf diese einfache Weise keine komplexeren Sachverhalte abbilden lassen. Dass für die Beispielsprache das Code-Folding für Bereiche von $CMS_SET()$ bis $CMS_END_SET$ und von $CMS_IF()$ bis $CMS_END_IF$, aber nicht für Bereiche von $CMS_SET()$ bis $CMS_END_IF$ gelten soll, lässt sich nicht in zwei einfachen regulären Ausdrücken abbilden. Visual Studio Code kennt jedoch zwei erweiterte Formen der Definition: Das Anbinden einer API-Schnittstelle und der Einsatz eines Sprachservers.

Abbildung 4 zeigt einen weiteren Nachteil des gezeigten Vorgehens: Die regulären Ausdrücke sorgen für Code-Folding von $CMS_ELSE$ bis $CMS_END_IF$. Dadurch ist $CMS_IF$ frei, um es bis zu $CMS_END_FOR$ einzuklappen. Ein weiteres Code-Folding ergibt sich somit zwischen den nicht zusammenhängenden Ausdrücken $CMS_FOR$ aus der obersten Zeile und dem $CMS_END$.

Falsches Code-Folding für ein if-else-Konstrukt (Abb. 4)