Code-Blöcke sollten in ein <code>
-Element eingeschlossen werden, dass seinerseits in einem <pre>
-Element steckt. Die jeweilige Programmiersprache des Code-Blockes sollte als CSS-Klasse im Format language-PROGRAMMIERSPRACHE
angegeben werden:
<pre><code class="language-javascript">if (options.debug) {
console.log("Options: ", options);
}</code></pre>
Die umschließenden <pre>
- und <code>
-Tags sollten in die gleiche Zeile wie der folgende Code gesetzt werden, damit es keine hässlichen Leerzeilen am Anfang und Ende des Code-Blockes gibt.
Weil (fast) alles an Markup den Markdown-Prozessor unverändert passiert, kann man natürlich die Code-Blöcke einfach von Hand als HTML eingeben, so wie gerade gezeigt:
<pre><code class="language-javascript">if (options.debug) {
console.log("Options: ", options);
}</code></pre>
So-genannte fenced, also “umzäunte” Code-Blöcke werden von den meisten Markdown-Prozessoren unterstützt. Solche Code-Blöcke werden von dreifachen Backticks ``` umschlossen:
```
if (options.debug) {
console.log("Options: ", options);
}
```
Dies erzeugt exakt das Konstrukt, das wir benötigen, nämlich ein <code>
-Element innerhalb eines <pre>
-Elementes. Allerdings hat keines der Tags irgendwelche Attribute. Genauer gesagt fehlt das Class-Attribut class="language-javascript"
. Man kann aber die Sprache einfach nach den öffnenden Backticks angeben:
```javascript
if (options.debug) {
console.log("Options: ", options);
}
```
Es gibt im Moment allerdings noch ein kleines Problem damit. Der Default-Markdown-Prozessor von Qgoda ist noch Text::Markdown, das Fenced Code-Blocks mit Sprachangabe nicht unterstützt. Stattdessen interpretiert es die Sprachangabe als Teil des Codes. Diese Syntax lässt sich deshalb nur verwenden, wenn Text::Markdown::Hoedown
als Markdown-Prozessor verwendet wird.
Es ist geplant,
Text::Markdown
als Qgodas standardmäßigen Markdown-Prozessor durch
Text::MultiMarkdown
zu ersetzen, siehe
https://github.com/gflohr/qgoda/issues/55.
Text::MultiMarkdown
unterstützt fenced Code-Blocks mit Sprachangabe.
PrismJS ist ein populärer Syntax-Highlighter, der vollständig in JavaScript geschrieben ist und eine beeindruckende Anzahl an Sprachen unterstützt.
… PrismJS auch Template Toolkit unterstützt? Dazu muss einfach
tt2
als Sprachname angegeben werden
Die minimal benötigen JavaScript- und CSS-Dateien lassen sich dem folgenden Code-Schnipsel entnehmen:
<!doctype html>
<html>
<head>
<link href="/assets/css/prismjs/themes/prism.css" rel="stylesheet" />
</head>
<body>
<script href="/assets/js/prismjs/prism.js"></script>
</body>
</html>
Das Standard-Stylesheet wird in Zeile 4 eingebunden, und die Core-Bibliothek in Zeile 7. Es muss natürlich sichergestellt sein, dass sie an den entsprechenden Stellen auch gefunden werden.
Mit PrismJS wird eine Reihe von Themes ausgeliefert. Das Default-Theme lässt sich überschreiben, indem eine weitere Stylesheet-Datei eingebunden wird:
<link href="/assets/css/prismjs/themes/prism.css" rel="stylesheet" />
<link href="/assets/css/prismjs/themes/prism-coy.css" rel="stylesheet" />
So lässt sich der Stil auf das “Coy”-Theme umstellen.
Um Speicher und Bandbreite zu sparen, lädt PrismJS nicht alle Highlighter-Komponenten und Plug-Ins automatisch. Sie müssen vielmehr explizit angegeben werden.
Das untenstehende, vollstdändige Beispiel würde Highlighting für die Sprache JavaScript und das Plug-In line-numbers
aktivieren. Mit Hilfe dieses Plug-ins wird mit CSS allen Zeilen die Zeilennummer vorangestellt:
<!doctype html>
<html>
<head>
<link href="/assets/css/prismjs/themes/prism.css" rel="stylesheet" />
<link href="/assets/css/prismjs/themes/prism-coy.css" rel="stylesheet" />
<link href="/assets/css/prismjs/plugins/line-numbers/prism-line-numbers.css" rel="stylesheet" />
</head>
<body>
<script href="/assets/js/prismjs/prism.js"></script>
<script href="/assets/js/prismjs/plugins/line-numbers/prism-line-numbers"></script>
<script href="/assets/js/prismjs/components/prism-javascript.js"></script>
</body>
</html>
Um Unterstützung für weitere Sprachen zu aktivieren, müssen nach Zeile 11 lediglich die entsprechenden weiteren PrismJS-Komponenten hinzugefügt werden.
Fenced Code-Blöcke sind nicht immer ausreichend:
Das Qgoda-Highlighter-Plug-in wird wie jedes andere Plug-in installiert, normalerweise also so:
$ cd /path/to/your/project
$ npm install gflohr/qgoda-plugin-tt2-highlight
Mit yarn
statt npm
lautet das Kommando yarn add gflohr/qgoda-plugin-tt2-highlight
.
Jetzt wird die Syntax-Hervorhebung mit Direktiven für Template Toolkit aktiviert:
[% USE Highlight %]
[% FILTER $Highlight "language-javascript" "line-numbers"
"data-start"=42 %]
if (options.debug) {
console.log("Options: ", options);
}
[% END %]
Alle positionellen Argument für das Fitler-Plug-In (Zeile 2) werden der CSS-Klasse des umgebenden <pre>
-Elements zugefügt. Die benannten Argument dagegen werden in HTML-Attribute und ihre entsprechenden Werte umgewandelt.
Positional arguments in Template Toolkit are standalone arguments, as opposed
to named arguments which have the form key=value
.
Note that you have to quote all keys that contain non-alphanumeric characters! Values always have to be quoted (unless they refer to a variable).
Das obige Beispiel erzeugt den folgenden HTML-Code:
<pre class="language-html line-numbers" data-start="5">if (options.debug) {
console.log("Options: ", options);
}</code></pre>
Provided that you have correctly loaded the PrismJS line-numbers
plug-in, this
will highlight the code as JavaScript in the browser, add line numbers in front of every
line and start the line numbering with line 5. For
instance, the code examples on this very page are
produced with directives like this:
Es ist nicht unwahrscheinlich, dass sämtliche Code-Beispiele auf einer Seite in der gleichen Programmiersprache geschrieben sind, und deshalb die gleichen Einstellungen verwenden sollte. Deshalb kann man auch globale, für die ganze Seite geltende Einstellungen direkt in der USE
-Direktive vergeben, mit der der Filter geladen wird.
[% USE Highlight "language-javascript" "line-numbers" %]
...
[% FILTER $Highlight %]
if (options.debug) {
console.log("Options: ", options);
}
...
[% END %]
Alle FILTER
-Aufrufe teilen nun die gleichen Einstellungen. Genauer gesagt erhalten sie Die Summe aller Argumente, die für USE
und für FILETR
übergeben wurden.
Um eine bestimmte CSS-Klasse für einen einzelnen "FILTER" zu deaktivieren, übergibt man diesen Klassennamen mit vorangestelltem Minuszeichen (-):
...
[% FILTER $Highlight "-language-javascript" "language-html" %]
<link href="styles.css" rel="stylesheet">
...
[% END %]
Dieser Codeblock wird nun ausnahmsweise nicht als JavaScript, sondern als HTML hervorgehobe.
Die Verwendung des Qgoda-Syntax-Highlighter-Plug-ins ist sehr flexibel, bedeutet aber auch eine Menge Tipparbeit im Vergleich zu fenced Code-Blocks. Auf dieser Site verwenden wir stattdessen in der Regel einen kleinen JavaScript-Hack, der es erlaubt, das Plug-In `line-numbers" direkt mit fenced Code-Blocks zu aktivieren:
```javascript;line-numbers
if (options.debug) {
console.log("Options: ", options);
}
```
See the JavaScript source code for details!
Es gibt zwei kleine Problemchen, die man beachten muss.
Code-Blöcke ohne Sprachangabe
Benutzt man fenced Code-Blocks oder das Qgoda-Plug-In für Syntax-Hervorhebung ohne Angabe einer Sprache, wird der entsprechende Block von PrismJS ignoriert. Das wird in einer späteren Version des Plug-Ins behoben werden (siehe https://github.com/gflohr/qgoda-plugin-tt2-highlight/issues/1 und https://github.com/gflohr/qgoda/issues/51). Bis dahin muss man entweder jeden betreffenden Code-Block mit der Sprache “None” oder mit der CSS-Klasse “language-none” markieren, oder wieder etwas JavaScript bemühen:
var codes = document.querySelectorAll('pre>code');
for (var i = 0; i < codes.length; ++i) {
var parent = codes[i].parentElement;
if (!parent.hasAttribute('class'))
parent.setAttribute('class', 'language-none');
}
Man können meinen, dass die Prüfung in Zeile 4 pre
-Elemente ignoriert, die ein Class-Attribut haben, aber keine Angabe von language-*
. Das kann allerdings nicht passieren, weil alle Blöcke generiert sind, und sie eben kein Class-Attribut haben.
Gut, das ist nicht 100 % wahr. Hat man solch einen Block mit dem Highlighter-Plug-In erstellt, könnte es ein Class-Attribut aber keine Sprachangabe haben. Aber das ist deine eigene Schuld, und es ist offensichtlich, wie man das Problem behebt.
css-modules
und line-numbers
Das andere Problem ist etwas esoterisch und wird durch einen kleine Mangel im Plug-In-System von PrismJS verursacht. Das Plug-In css-modules
dient dazu, automatisch Namensräume für CSS-Klassen und ID-Attribute nach BEM-Methodologie zu verwenden.
Das Plug-In line-numbers
dagegen ignoriert BEM. Es sucht nach Code-Blöcken, die exakt die Klasse line-numbers
haben. Die Lösung des Problems besteht darin, einfach beide Klassen zu verwenden, also sowohl mit BEM-Präfix zum Stylen als auch die Variante ohne Präfix für den JavaScript-Code des Plug-Ins.
You can see how this is done in the source code of this page.
The BEM-style class name is stored in the variable css.prism.line_numbers
. The hash
containing that variable is read in
https://github.com/gflohr/qgoda-site/blob/master/_views/functions/css-modules.tt
and that function is included at the top of
source code of this page.