A very common requirement is to have syntax-highlighting applied to code blocks. Best practice with Qgoda is to just mark code blocks semantically correct and let the client do the highlighting when rendering the generated pages.
Code blocks should be wrapped into a <code>
element that is in turn wrapped into a
<pre>
element. The programming language of the code block should be added as a
CSS class in the format language-PROGRAMMING-LANGUAGE
:
<pre><code class="language-javascript">if (options.debug) {
console.log("Options: ", options);
}</code></pre>
It is advisable to put the enclosing <pre>
and <code>
tags on the same line as
the enclosed code. Otherwise ugly line feeds are added to the beginning and the end
of the block.
Remember that (almost) all markup goes through unchanged in Markdown. Nobody stops you from entering your code blocks manually with HTML, as we have just seen:
<pre><code class="language-javascript">if (options.debug) {
console.log("Options: ", options);
}</code></pre>
So-called fenced code blocks are supported by many markdown processors. Fenced code blocks are surrounded by triple backticks ```:
```
if (options.debug) {
console.log("Options: ", options);
}
```
This creates exactly the construct we want, the code inside of a <code>
element
inside of a <pre>
element. But none of the tags has any attributes, more
specifically the class="language-javascript"
is not present. But you can
specify the language immediately following the opening fence
:
```javascript
if (options.debug) {
console.log("Options: ", options);
}
```
There is a caveat, though. Qgoda's default Markdown processor
Text::Markdown does not support fenced code blocks with a language specifier.
It erroneously interprets it as part of the code. You can therefore only
use the language specifier, when you use Text::Markdown::Hoedown
as your
markdown processor.
There are plans to replace
Text::Markdown
with
Text::MultiMarkdown
as Qgoda's default markdown processor,
see
https://github.com/gflohr/qgoda/issues/55.
Text::MultiMarkdown
supports fenced code blocks with a language specifier.
PrismJS is a very popular syntax highlighter written in Javascript that supports an impressive list of languages.
... that PrismJS can also highlight Template Toolkit source code? Use
tt2
as the language name and you're all done.
You can see the minimum required JavaScript and css files in the following code snippet:
<!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>
The default stylesheet gets included in line 4 and the core library in line 7. Make sure that they are found at the locations specified.
PrismJS ships with a number of themes. You can override the default theme by loading an additional stylesheet:
<link href="/assets/css/prismjs/themes/prism.css" rel="stylesheet" />
<link href="/assets/css/prismjs/themes/prism-coy.css" rel="stylesheet" />
That will change the style to the coy
theme.
In order to save memory and band-width, PrismJS does not load all of its highlighters and plug-ins automatically but you have to explicitely specify them.
The complete example below would load support for highlighting JavaScript and also
load the line-numbers
plug-in that uses CSS to prepend every line with a line
number:
<!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>
If you want to have syntax highlighting for more languages, just add the corresponding PrismJS component after line 11.
Fenced code-blocks are not always enough:
You can install the Qgoda highlighter plug-in like any other Qgoda plug-in. Normally:
$ cd /path/to/your/project
$ npm install gflohr/qgoda-plugin-tt2-highlight
You can use yarn add gflohr/qgoda-plugin-tt2-highlight
if you prefer yarn
over
npm
.
Now you have to use Template Toolkit directives in order to activate the highlighting:
[% USE Highlight %]
[% FILTER $Highlight "language-javascript" "line-numbers"
"data-start"=42 %]
if (options.debug) {
console.log("Options: ", options);
}
[% END %]
All positional arguments to the filter plug-in (line 2) are added to the CSS
class of the surrounding <pre>
element. The named arguments are converted
to HTML attributes and their corresponding values.
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).
The above example will result in the following 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:
It is quite likely that all code blocks on a page are in
the same programming language and should share their
settings. You can therefore pass global arguments in
the USE
directive, when you load the filter.
[% USE Highlight "language-javascript" "line-numbers" %]
...
[% FILTER $Highlight %]
if (options.debug) {
console.log("Options: ", options);
}
...
[% END %]
All FILTER
invocations will now share the same settings,
respectively, they will receive the sum of the arguments
that you specified for USE
and those for FILTER
.
If you want to disable a certain CSS class for an
individual FILTER
, just pass the class name with a
minus sign (-) prepended:
...
[% FILTER $Highlight "-language-javascript" "language-html" %]
<link href="styles.css" rel="stylesheet">
...
[% END %]
This code block will now as an exception not be highlighted as JavaScript but as HTML.
Using the Qgoda syntax highlighter plug-in
is very flexible but involves a lot of typing compared to fenced code
blocks. On this side we use a little JavaScript
hack that allows us to enable the line-numbers
plug-in with fenced
code blocks like this:
```javascript;line-numbers
if (options.debug) {
console.log("Options: ", options);
}
```
See the JavaScript source code for details!
There are two little gotchas that you have to keep in mind.
If you use a fenced
code block or the qgoda highlighter plug-in without a language specification,
PrismJS will not highlight the code block. This will be fixed in a later
version of the highlighter plug-in (see
https://github.com/gflohr/qgoda-plugin-tt2-highlight/issues/1
and https://github.com/gflohr/qgoda/issues/51). Until then, you either have to
mark every affected code block as language none
(or with the class language-none
) or
you have to resort to a little bit of JavaScript:
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');
}
You may argue that the check in line 4 will miss pre
elements that have a class attribute
but miss a language-*
specification. This cannot happen because all of the blocks
are generated, and they will not have any class attribute set.
Well, not 100 % true. If you created such a block with the highlighter plug-in, it could have a class attribute but no language specification. But that's your own fault then, and you know how to fix it.
The other glitch is rather esoteric and caused by a little flaw in the plug-in system
of PrismJS. Its css-modules
plug-in can be used to automatically use the
BEM methodology for automatically namespacing all CSS
class names and id attributes.
The line-numbers
plug-in on the other hand ignores BEM. It searches for code blocks
that have the exact class line-numbers
. The workaround is to use both classes,
the BEM-style class for styling and the literal one for the JavaScript code.
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.