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.
- Nils Andresen
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.
Erste Schritte zur Vorbereitung
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).
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.
Zusammenhänge und Überblick
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).
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 inlineComment
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": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
]
}
Farben für die Syntax
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 TeilKommando
– 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 mitentity.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": "--\\$"
}
Patchwork mit Codeschnipseln
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.
Deklaratives Falten
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$
.