Warum Code Snippets?
Code Snippets sind kleine Textbausteine, die dem Programmierer Tipparbeit ersparen können, indem häufig vorkommende Textstrukturen als eine Art dynamische Textbausteine erstellt werden können. Der Zusatz „dynamisch“ bezieht sich hierbei auf besondere Eigenschaften, die Code Snippets bieten:
Zwar bringt auch bereits manche Extension für VS Code, wie z.B. die Microsoft AL Language Extension, bereits verschiedene Code Snippets mit, jedoch ist die Qualität dieser Code Snippets oftmals unzureichend. Hinzukommt, dass Code Snippets zumeist auf die Arbeitsweise eines bestimmten Programmierers zugeschnitten sind und daher häufig nicht jeden Programmierer ansprechen.
In diesem Post geht es daher vorrangig darum, Beispiele aufzuzeigen, wie man Code Snippets erstellt und was sich mit Ihnen erreichen lässt. Jeder Programmierer möge sich sodann seine eigenen individuellen Code Snippets daraus ableiten.
Eine vollständige Dokumentation findet sich unter: https://code.visualstudio.com/docs/editor/userdefinedsnippets
Wo werden Code Snippets gespeichert?
Code Snippets können an verschiedenen Orten abgelegt werden:
Eine Code Snippet-Datei darf durchaus auch mehrere Code Snippets enthalten. Zu beachten ist jedoch, dass insbesondere wenn Code Snippets im Ordner .vscode abgelegt werden sollen, diese in einer Datei mit der Dateinamenserweiterung „.code-snippets“ gespeichert werden müssen und nicht in weiteren Unterordnern organisiert sein dürfen. Von diesen Dateien darf es dann aber auch wiederum mehrere geben.
Weiterhin ist zu beachten, dass in dem Moment, in dem die Programmiersprache, für die die Code Snippets gelten sollen, nicht mehr am Dateinamen der Code Snippet-Datei zu erkennen ist, diese über den Schlüssel „scope“ dem Code Snippet hinzugefügt werden sollte.
So ist VS Code auf Grund des Dateinamens bekannt, dass Code Snippets in der Datei „al.json“ für die Sprache AL gelten sollten. Nennen Sie die Datei jedoch z.B. „page.code-snippets“ fügen Sie innerhalb jedem der enthaltenen Code Snippets z.B. direkt nach dem Schlüssel „description“ die folgende Zeile hinzu:
"scope": "al"
Erstellen eines Code Snippets
Für die ersten Schritte wählen wir den einfachen Weg über die VS Code-Einstellungen. Klicken Sie dazu auf das Zahnradsymbol in der linken unteren Ecke und wählen Sie „User Snippets“.
Aus der Liste wählen Sie nun den Eintrag „al.json“ aus:
Es öffnet sich daraufhin die Datei %AppData%\Code\User\snippets\al.json, in der zunächst ein JSON-Objekt mit { } umfasst vorgegeben ist, das derzeit nur Kommentarzeilen mit Erklärungen enthält. Unter dem Kommentar in der Zeile „// Example:“ finden Sie ein Beispiel für ein einfaches Code Snippet. Sie können dieses Beispiel kopieren, aber achten Sie darauf, dass sowie Sie das Beispiel mehrfach für weitere Code Snippets verwenden, diese durch Kommata separiert werden müssen:
{
" Code Snippet 1": {
// …
} ,
"Code Snippet 2": {
// …
}
}
Zudem müssen Sie darauf achten, dass alle Code Snippets einen eindeutigen Schlüsselnamen verwenden. Im obigen Beispiel sind „Code Snippet 1“ und „Code Snippet 2“ solche Schlüsselnamen.
Code Snippet-Beispiele
Im Folgenden schauen wir uns nacheinander ein paar Beispiele an. Beginnend bei einer einfachen Textzeile, betrachten wir nach und nach immer komplexere Beispiele. Ich habe hierfür Beispiele ausgewählt, die sich auch in der Praxis vortrefflich nutzen lassen.
Einfacher Text: Hello World!
Beginnen wir mit einem einfachen Beispiel, einer „Hello World“-Message. Im Ergebnis soll der erzeugte Code so aussehen:
Message('Hello World!');
Das Code Snippet dazu sieht folgendermaßen aus:
{
"Hello World“: {
"prefix": "myhelloworld",
"description": "Inserts a simple Hello World!-Message",
"body": "Message('Hello World!');"
}
}
In diesem Beispiel wird ein Code Snippet definiert, dessen Body aus lediglich einem String besteht.
Beachten Sie dabei die unterschiedlichen Anführungszeichen: String-Literale in JSON werden in doppelte Anführungszeichen gesetzt, der Text „Hello World!“ ist hingegen in einfache Hochkommata gefasst, da es sich hierbei um ein AL-Textliteral handelt.
Sollten Sie einmal innerhalb eines JSON-Strings für den Inhalt doppelte Anführungszeichen verwenden wollen, wie das z.B. für AL-Symbolnamen mit Sonderzeichen erforderlich wäre, müssen Sie diesen einen Backslash voranstellen, z.B.:
"body": "group(\"Group No. 1\")".
Speichern Sie die Datei mit dem Beispiel und wechseln Sie in eine AL-Code-Datei. Beginnen Sie, „myhelloworld“ einzutippen. Nach wenigen Zeichen sollte Intellisense bereits Ihr Code Snippet vorschlagen, welches Sie zum Einfügen an der aktuellen Cursor-Position nur noch auswählen müssen.
Sie haben damit Ihr erstes Code Snippet erstellt. Als nächstes schauen wir uns schrittweise komplexere Beispiele an.
Mehrzeiliger Text: Factbox area
Sicherlich ist Ihnen auch schon aufgefallen, dass Entwickler häufig vergessen, die System-Factboxes zu Pages hinzuzufügen. Selbst die Code Snippets der Microsoft AL Language Extension sowie der Page Wizard aus den AZ AL Dev Tools versäumen dies. Grund genug, sich ein Code Snippet zu bauen, dass dies mit wenigen Tastaturanschlägen nachholt.
Im Ergebnis soll der erzeugte Code so aussehen:
area(FactBoxes)
{
systempart(Links; Links) { ApplicationArea = RecordLinks; }
systempart(Notes; Notes) { ApplicationArea = Notes; }
}
Da das Code Snippet mehrzeilig ist, müssen wir den Body des Code Snippets als ein Array von Strings festlegen:
"body": [
"Zeile 1",
"Zeile 2",
"Zeile 3"
]
Das Code Snippet sieht dann folgendermaßen aus:
"Default Factbox Area": {
"prefix": "myfactboxarea",
"scope": "al",
"description": "Adds a default factbox area",
"body": [
"area(FactBoxes)",
"{",
" systempart(Links; Links) { ApplicationArea = RecordLinks; }",
" systempart(Notes; Notes) { ApplicationArea = Notes; }",
"}"
]
}
Mit Platzhaltern: Table Field
Wenn Sie einer AL-Tabelle neue Felder hinzufügen möchten, ist es stets mühsam, neben dem Feldnamen die zumeist gleichlautende Caption einzugeben. Ein Code Snippet kann uns diese Arbeit abnehmen, indem wir den Feldnamen durch einen Platzhalter ersetzen und diesen in der Caption wiederverwenden.
Dabei stellt sich jedoch das Problem, dass Feldnamen, die Sonderzeichen enthalten, in doppelte Anführungszeichen gesetzt werden müssen, die Caption hingegen als Textliteral in einfachen Hochkommata steht. Dieses Problem lässt sich jedoch sehr einfach lösen, indem wir den Feldnamen von vornherein in Anführungszeichen setzen. Sollte diese nicht nötig sein, weil der Feldname keine Sonderzeichen enthält, werden diese durch die Dokumentformatierung beim Speichern automatisch entfernt.
Im Ergebnis soll der erzeugte Code so aussehen:
field(id; "name"; datatype)
{
Caption = 'Fieldname';
│
}
Das Code Snippet sieht dann folgendermaßen aus:
"Table Field": {
"prefix": "mytablefield",
"description": "Inserts a new table field.",
"body": [
"field(${1:id}; \"${2:name}\"; ${3:datatype})",
"{",
" Caption = '$2';",
" $0",
"}",
]
}
In diesem Beispiel werden Platzhalter verwendet, die bei Einfügen des Code Snippets vom Programmierer wie ein Formular ausgefüllt werden können. Dabei werden die Texte „id“, „name“ und „datatype“ einerseits als Erklärung und andererseits als Vorgabewerte verwendet. Der Platzhalter $2 erscheint dabei zweimalig: einmal als Feldname und einmal als Caption. Beim Ausfüllen der Platzhalter verwendet VS Code Multi-Cursor, so dass beim Eintippen des gleichen Platzhalters parallel an allen Verwendungsstellen der eingegebene Text erscheint.
Der Platzhalter $0 steht schließlich für die Position des Cursors nachdem das Code Snippet vollständig eingefügt wurde.
Erzeugen des symbolischen Namens aus der Caption: Group
Häufig legt man sich in Pages eine neue Gruppe für Felder oder Actions an. Auch hier ist es lästig, zunächst den symbolischen Gruppennamen einzugeben und danach noch die voll ausgeschriebene Caption. Praktisch wäre es, lediglich die Caption einzutragen und das Code Snippet daraus den symbolischen Namen erzeugen zu lassen.
AL verwendet für die Schreibweise von Symbolen Pascal Case (jedes Wortes beginnt mit einem Großbuchstaben, keine Leerzeichen oder sonstige Sonderzeichen). Wir benötigen also eine Konvertierung des Textes der Caption in einen Pascal Case-Bezeichner, der in AL ohne die doppelten Anführungszeichen geschrieben werden darf.
Erfreulicher Weise bietet VS Code die Möglichkeit, Suchen & Ersetzen mit Hilfe regulärer Ausdrücke durchzuführen und für Pascal Case ist glücklicherweise eine Formatierungsoption verfügbar.
Im Ergebnis soll der erzeugte Code so aussehen:
group(BezeichnerGrp)
{
Caption = 'Beschriftung';
│
}
Das Code Snippet dazu sieht folgendermaßen aus:
"Group": {
"prefix": "mygroup",
"description": "Inserts a group into a page.",
"body": [
"group(${1/(.*)/${1:/pascalcase}/}Grp)",
"{",
" Caption = '$1';",
"",
" $0",
"}"
]
},
Schauen wir uns in der VS Code-Dokumentation die EBNF-Grammatik der regulären Ausdrücke an, finden wir folgende Regel für Tranformationen:
transform ::= ‚/‘ regex ‚/‘ (format | text)+ ‚/‘ options
Die Anwendung der Transformation auf einen Platzhalter sieht dann so aus:
${1/Regex/Ersetzer/}
Betrachten wir den regulären Ausdruck aus obigem Code Snippet genauer:
${1/(.*)/${1:/pascalcase}/}
Der reguläre Ausdruck liefert eine einzige Gruppe (), bestehend aus beliebigen Zeichen (.*). Im anschließenden Ersetzer kann die erste Gruppe mit $1 referenziert werden, die zweite mit $2 usw. Verwirrender Weise führt das leicht zu Verwechslung des Platzhalters mit dem Gruppen-Match, da beide als $1 erscheinen. Es handelt sich dabei aber um vollkommen verschiedene Dinge.
Der Ersetzer greift nun den gefunden Gruppen-Match $1 auf, also den Text der als Platzhalter eingegeben wird, und formatiert ihn mit Hilfe von /pascalcase/ um. Um dies zu verstehen, müssen wir uns auch noch die EBNF-Grammatik des Ersetzers „format“ anschauen:
format ::=
‚$‘ int |
‚${‚ int ‚}‘ |
‚${‚ int ‚:‘ ‚/upcase‘ | ‚/downcase‘ | ‚/capitalize‘ | ‚/camelcase‘ | ‚/pascalcase‘ ‚}‘
In unserem Falle sieht das Format folgendermaßen aus:
format ::= '${' int ':' '/pascalcase'
Dabei wird an Stelle von int die Gruppen-Id (also 1) eingesetzt:
${1:/pascalcase}/
Abschließend hängen wir noch die Abkürzung „Grp“ an, um etwaigen Namenskonflikten aus dem Weg zu gehen. So lässt sich auch leichter bei der Referenzierung durch andere Extensions erkennen, dass es sich dabei um eine Gruppe handelt.
Variable im Code Snippet verwenden: Report Layout-Dateiname
VS Code bietet eine Vielzahl von Variablen, die in ein Code Snippet eingebaut werden können. Diese beginnen allesamt mit einem „$“ und können direkt im Code Snippets verwendet werden.
$TM_SELECTED_TEXT |
Der aktuell markierte Text oder ein Leer-String |
$TM_CURRENT_LINE |
Der Inhalt der aktuellen Zeile |
$TM_CURRENT_WORD |
Das Wort unter dem Cursor ode rein Leer-String |
$TM_LINE_INDEX |
Die null–basierte Zeilennummer |
$TM_LINE_NUMBER |
Die eins–basierte Zeilennummer |
$TM_FILENAME |
Der Dateiname des aktuellen Dokumentes |
$TM_FILENAME_BASE |
Der Dateiname des aktuellen Dokumentes ohne Dateinamenerweiterung |
$TM_DIRECTORY |
Der Verzeichnisname des aktuellen Dokumentes |
$TM_FILEPATH |
Der vollständige Pfad und Dateiname des aktuellen Dokumentes |
$RELATIVE_FILEPATH |
Der Pfad des aktuellen Dokumentes relative zum geöffneten Workspace oder Ordner |
$CLIPBOARD |
Der Inhalt der Zwischenablage |
$WORKSPACE_NAME |
Der Name des geöffneten Workspaces oder Ordners |
$WORKSPACE_FOLDER |
Der Pfad des geöffneten Workspaces oder Ordners |
$CURSOR_INDEX |
Der null-basierte Index der Cursor-Nummer |
$CURSOR_NUMBER |
Der eins-basierte Index der Cursor-Nummer |
Darüber hinaus stehen weitere Platzhalter zur Verfügung, z.B. für aktuelles Datum, Zeit, Zufallswerte, sprachgerechte Kommentierung etc. Details hierzu finden Sie in der Dokumentation der Code Snippets.
Für das nächste Beispiel wählen wir uns das RdlcLayout-Property eines Report-Objektes. Üblicherweise versucht man als Programmierer, die .al-Datei eines Reports mit seiner -rdlc-Layout-Datei beieinander zu halten, da Änderungen an Berichten zumeist beide Dateien betreffen und die Gefahr einer Verwechslung des Layouts mit einem anderen Bericht ansonsten zu groß wäre.
Das RdlcLayout-Property kann hierfür automatisch aus dem relativen (relativ zum Ordner der App hin gesehenen) Pfad- und Dateinamen der .al-Datei gebildet werden.
Für einen in der Datei „.\src\report\MyReport.al“ definierten Report soll der erzeugte Code im Ergebnis so aussehen:
RDLCLayout = '.\src\report\MyReport.rdlc';
Das Code Snippet dazu sieht folgendermaßen aus:
"RDLC Layout Filename": {
"prefix": "myrdlclayoutfilename",
"description": "Inserts the default RDLC layout path and filename.",
"body": [
"RDLCLayout = '.\\\\${RELATIVE_FILEPATH/(.*)\\..+$/$1/}.rdlc';$0",
]
}
Auch in diesem Beispiel verwenden wir wieder einen regulären Ausdruck. Zu beachten ist hier, dass wir nun einen Backslash nicht nur durch das Escape-Zeichen verdoppeln müssen \\ sondern sogar vervierfachen.
Entnehmen wir dem Code Snippet den entscheidenden Teil:
${RELATIVE_FILEPATH/(.*)\\..+$/$1/}
Die Variable $RELATIVE_FILEPATH wird in den relativen Pfadnamen aufgelöst und sodann der reguläre Ausdruck darauf angewandt. Der enthaltene reguläre Ausruck sieht folgendermaßen aus:
(.*)\\..+$
Der reguläre Ausdruck sucht einen Match aller Zeichen bis zum letzten Punkt \. (dem Punkt als Zeichen muss das Escape-Zeichen vorangestellt werden) gefolgt von weiteren Zeichen bis zum String-Ende +$. Im Ergebnis erhalten wir den relativen Dateipfad ohne Dateierweiterung, damit wir die Erweiterung „.al“ durch „.rdlc“ ersetzen können.
Mit $0 sorgen wir dafür, dass der Cursor im Anschluss am Ende der Zeile steht.
Surround With: Report-Regions
In Reports ist es eine Best Practice, die Columns und die Triggers eines DataItems mit einer eigenen Region zu umklammern, damit sich diese zwecks besseren Überblick zusammenfalten lassen. Hierzu sei insbesondere der Shortcut <Strg>+<K><8> zu erwähnen, mit dem sich auf einen Schlag alle Regions der Datei zusammenfalten lassen. Hiermit lässt sich der Report in seiner Gesamtheit viel schneller und besser erfassen.
Im Ergebnis soll der erzeugte Code so aussehen:
[…]
dataitem(Customer; Customer)
{
ReqFilterFields = "No.", "Country/Region Code";
PrintOnlyIfDetail = true;
#region Columns
column(No; "No.") { Include Caption = true; }
column(Name; Name) { IncludeCaption = true; }
#endregion
[…]
#region Triggers of: Customer
trigger OnPreDataItem();
begin
// …
end;
trigger OnAfterGetRecord();
begin
// …
end;
#endregion
}
[…]
Falten Sie nun mit <Strg>+<K><8> alle Regions zusammen, sieht der Code so aus:
[…]
dataitem(Customer; Customer)
{
ReqFilterFields = "No.", "Country/Region Code";
PrintOnlyIfDetail = true;
#region Columns
[…]
#region Triggers of: Customer
}
[…]
Bestünde der Bericht nun aus mehreren verschachtelten oder auch nicht verschachtelten DataItems mit jeweils vielen Spaltendefinitionen, hätten Sie nunmehr durch das Zusammenfalten der Regions einen bedeutend besseren Überblick über die Struktur des Berichtes.
Wir benötigen hierfür zwei Code Snippets, die jeweils den markierten Text einfügen. Da der Text jedoch markiert sein muss, können wir nun nicht einfach den Code Snippet-Namen eintippen, denn dadurch würden wir den markierten Text überschreiben. Stattdessen verwenden wir den Befehl (<Strg>+<P>) „Snippets: Surround With Snippet…“ bzw. den Shortcut <Alt>+<P>, <S>. Hiermit können wir das zu verwendende Code Snippet aus einer Liste derjenigen Code Snippets wählen, die sich auf markierten Text beziehen. Die anderen Code Snippets werden praktischerweise nicht in dieser Liste angezeigt. Tipp: Auch der Shortcut für Quick Fixes (<Strg> + <.>) zeigt uns diese Code Snippets an.
Um die Code Snippets zu verwenden, werden zuerst alle „column“-Zeilen vollständig markiert und dann das Columns-Region-Code Snippet darauf angewandt. Anschließend markiert man alle Trigger und wiederholt das gleiche mit dem Triggers-Region-Code Snippet. Bei Letzterem kann nun noch der Name des DataItems angegeben werden, was hilfreich ist, wenn der Report einmal länger wird und viele verschachtelte DataItems aufweist. Bei den Columns ist dies nicht erforderlich, da diese ja unter der DataItem-Definition direkt nach den Properties stehen.
Die Properties hingegen würde man nicht in eine Region verpacken, da diese nach dem Zusammenfalten des gesamten Berichtsobjektes noch sichtbar sein sollten, um die Logik der DateItems schneller erfassen zu können.
Das Column-Region-Code Snippet sieht dann folgendermaßen aus:
"Report Column Region": {
"prefix": "myreportcolumnregion",
"scope": "al",
"description": "Surrounds the selection with a column region",
"body": [
"#region Columns",
"${TM_SELECTED_TEXT}",
"#endregion"
]
}
Das Triggers-Region-Code Snippet sieht folgendermaßen aus:
"Report Triggers Region": {
"prefix": "myreportTriggersRegion",
"scope": "al",
"description": "Surrounds the selection with a column region",
"body": [
"#region Triggers of: ${1:DataItemName}",
"${TM_SELECTED_TEXT}",
"#endregion"
]
Zusammenfassung
Wir haben anhand verschiedener Beispiele gesehen, wie sich Code Snippets geschickt einsetzen lassen, um unbequeme und aufwendige Editier-Aufgaben im Code zu vereinfachen. Die aufgezeigten Beispiele sind zwar für sich genommen schon praxisgerecht, sollten aber darüber hinaus auch dazu dienen, weitere, eigene Code Snippets zu entwerfen. Letztendlich wird so jeder Programmierer seinen eigenen, ganz individuellen Satz an Code Snippets vorrätig halten, die genau auf seine Arbeitsweise angepasst sind.
Abschließend sei erwähnt, dass Code Snippets sich zudem, wie alle anderen Konfigurationen in VS Code auch, in Profilen ablegen lassen.
Links und Referenzen