Syntax Highlighting
Guide on ways to implement code syntax coloration in Hugo
Communication #
Sharing information is a essential for us humans that need to cooperate in order to survive.
Verbal communication, or speech, empowers us to express and convey more complex information than non-verbal communication. It relies on a common language, one that is shared by the parties involved.
Writing systems are methods to visually represent the verbal communication. They are based on scripts which are sets of symbols, such has letters for alphabetical written systems and punctuation. The Unicode project inventoried around 150 000 of such symbols.
They also use syntax which is set of rules defining the possible combination of the symbols.
Formal Languages #
Languages we use to exchange information between each others are natural. They use a more or less codified (grammar, orthography etc.) but everyone is free to apply these rules more or less thoroughly, because they favor semantics, the meaning of what we say over syntax. That is feature is recognizable in slang, neologism and sometimes poetry.
On the other hand, when a human tries to communicate instructions to a computer, it needs to use a formal language (aka the code), which intricately ties syntax and semantics.
Coloration #
Syntactic coloration eases the pain of reading code by highlighting tokens which commonly include:
Keywordsthat are reserved words (if,else,return, etc.)Namesfor variable or function identifiersLiteralsvalues (integers, strings etc.)Operatorsfor arithmetic or comparisonPunctuationincluding separators ((,[, etc.)- …
Server-side rendering #
Hugo is shipped with chroma, a syntax highlighter written in Go based on Pygments.
Its executed at build-time on the code chunks based on their language
attribute. The language is matched to a list of supported lexers which define
regular expressions for a universal quite granular
set of tokens.
Found tokens are wrapped in <span> tags.
I personally recommend to set markup.highlight.noClasses to false and let
chroma use classes instead of inline styles as
- it allows for easy dark and light theme
- it will allow to apply stricter header security policy (
style-src: unsafe-inline;)
For a list of all available options see: Hugo Configure Markup.
Client-side alternative #
Most common alternative options include highlight.js and prism which offer somewhat richer features but are executed at run-time so that they are not available when visitors’ have javascript disabled and incur a performance loss (bad UX). Additionally, it seems to me like a waste of resources to force execution of the code at each page load when it can be done once for all (bad Design). So we sticked to chroma.
Richer content #
If data-lang, the code block shows the (i) language’s name floating in a button on the top-right corner. When the user clicks on the language name, it add a control panel next to it with 4 icons:
- Expand => toggleCodeBlockModal(id) -> opens up a modal
- Linenos => toggleCodeBlockLinenos() ->
- Wrap/Unwrap => toggleCodeBlockWrap() -> adds code_wrap
- CopyButton => copyCodeBlockContent() -> Copied for 200 Timeout
If code is an output, it doesn’t have data-lang.
1<div class="highlight">
2 <div class="controls">
3 <span class="linenos" title="Toggle Line Numbers"></span>
4 <span class="wrap" title="Wrap Code"></span>
5 <span class="expand" title="Expand Code"></span>
6 <span class="copy" title="Copy Code"></span>
7 </div>
8 <div class="chroma">
9 <table class="lntable">
10 <tbody>
11 <tr>
12 <td class="lntd">
13 <pre tabindex="0" class="chroma">
14 <code>
15 <span class="lnt">1</span>
16 </code>
17 </pre>
18 </td>
19 <td class="lntd">
20 <span class="lang">JSON</span>
21 <pre tabindex="0" class="chroma"> # DEFILABLE
22 <code class="language-r" data-lang="r">
23 <span class="line">
24 <span class="cl">Code</span>
25 </span>
26 </code>
27 </pre>
28 </td>
29 </tr>
30 </tbody>
31 </table>
32 </div>
33</div>
Code samples1 #
HTML #
| |
bash #
1#!/bin/bash
2
3###### CONFIG
4ACCEPTED_HOSTS="/root/.hag_accepted.conf"
5BE_VERBOSE=false
6
7if [ "$UID" -ne 0 ]
8then
9 echo "Superuser rights required"
10 exit 2
11fi
12
13genApacheConf(){
14 echo -e "# Host ${HOME_DIR}$1/$2 :"
15}
16
17echo '"quoted"' | tr -d \" > text.txt
C++ #
1#include <iostream>
2
3int main(int argc, char *argv[]) {
4
5 /* An annoying "Hello World" example */
6 for (auto i = 0; i < 0xFFFF; i++)
7 cout << "Hello, World!" << endl;
8
9 char c = '\n';
10 unordered_map <string, vector<string> > m;
11 m["key"] = "\\\\"; // this is an error
12
13 return -2e3 + 12l;
14}
CSS #
1@font-face {
2 font-family: Chunkfive; src: url('Chunkfive.otf');
3}
4
5body, .usertext {
6 color: #F0F0F0; background: #600;
7 font-family: Chunkfive, sans;
8 --heading-1: 30px/32px Helvetica, sans-serif;
9}
10
11@import url(print.css);
12@media print {
13 a[href^=http]::after {
14 content: attr(href)
15 }
16}
Markdown #
1# hello world
2
3you can write text [with links](http://example.com) inline or [link references][1].
4
5* one _thing_ has *em*phasis
6* two __things__ are **bold**
7
8[1]: http://example.com
9
10---
11
12hello world
13===========
14
15<this_is inline="xml"></this_is>
16
17> markdown is so cool
18
19 so are code segments
20
211. one thing (yeah!)
222. two thing `i can write code`, and `more` wipee!
Diff #
1Index: languages/ini.js
2===================================================================
3--- languages/ini.js (revision 199)
4+++ languages/ini.js (revision 200)
5@@ -1,8 +1,7 @@
6 hljs.LANGUAGES.ini =
7 {
8 case_insensitive: true,
9- defaultMode:
10- {
11+ defaultMode: {
12 contains: ['comment', 'title', 'setting'],
13 illegal: '[^\\s]'
14 },
15
16*** /path/to/original timestamp
17--- /path/to/new timestamp
18***************
19*** 1,3 ****
20--- 1,9 ----
21+ This is an important
22+ notice! It should
23+ therefore be located at
24+ the beginning of this
25+ document!
26
27! compress the size of the
28! changes.
29
30 It is important to spell
R #
1require(stats)
2
3#' Compute different averages
4#'
5#' @param x \code{numeric} vector of sample data
6#' @param type \code{character} vector of length 1 specifying the average type
7#' @return \code{centre} returns the sample average according to the chosen method.
8#' @examples
9#' centre(rcauchy(10), "mean")
10#' @export
11centre <- function(x, type) {
12 switch(type,
13 mean = mean(x),
14 median = median(x),
15 trimmed = mean(x, trim = .1))
16}
17x <- rcauchy(10)
18centre(x, "mean")
19
20library(ggplot2)
21
22models <- tibble::tribble(
23 ~model_name, ~ formula,
24 "length-width", Sepal.Length ~ Petal.Width + Petal.Length,
25 "interaction", Sepal.Length ~ Petal.Width * Petal.Length
26)
27
28iris %>%
29 dplyr::nest_by(Species) %>%
30 dplyr::left_join(models, by = character()) %>%
31 rowwise(Species, model_name) %>%
32 mutate(model = list(lm(formula, data = data))) %>%
33 summarise(broom::glance(model))
See knitr examples.
Output #
1summary(cars)
speed dist
Min. : 4.0 Min. : 2.00
1st Qu.:12.0 1st Qu.: 26.00
Median :15.0 Median : 36.00
Mean :15.4 Mean : 42.98
3rd Qu.:19.0 3rd Qu.: 56.00
Max. :25.0 Max. :120.00
Plot #
1mtcars$cyl <- as.factor(mtcars$cyl)
2plot(mpg ~ hp , data=mtcars,
3 col=cyl, pch=c(4,6,8)[mtcars$cyl], cex=1.2)
4legend("topright", legend=levels(mtcars$cyl),
5 pch = c(4,6,8),
6 col=levels(mtcars$cyl))
ggplot #
1library(ggplot2)
2ggplot(mtcars, aes(x=hp, y=mpg, color=cyl, shape=cyl)) +
3 geom_point(size=3)
Facets and message #
1plt <-
2ggplot(mtcars, aes(x=hp, y=mpg, color=cyl, shape=cyl)) +
3 geom_point(size=3) +
4 geom_smooth(method="lm", aes(fill=cyl))
5
6plt + facet_wrap(~cyl)
`geom_smooth()` using formula 'y ~ x'
Python #
1@requires_authorization(roles=["ADMIN"])
2def somefunc(param1='', param2=0):
3 r'''A docstring'''
4 if param1 > param2: # interesting
5 print 'Gre\'ater'
6 return (param2 - param1 + 1 + 0b10l) or None
7
8class SomeClass:
9 pass
10
11>>> message = '''interpreter
12... prompt'''
Rust #
1#[derive(Debug)]
2pub enum State {
3 Start,
4 Transient,
5 Closed,
6}
7
8impl From<&'a str> for State {
9 fn from(s: &'a str) -> Self {
10 match s {
11 "start" => State::Start,
12 "closed" => State::Closed,
13 _ => unreachable!(),
14 }
15 }
16}
17
18use std::fs::File;
19use std::io;
20use std::io::Read;
21
22fn read_username_from_file() -> Result<String, io::Error> {
23 let mut f = File::open("hello.txt")?;
24 let mut s = String::new();
25 f.read_to_string(&mut s)?;
26 Ok(s)
27}
Shell #
Show unselectable prompt: $ (or # when root) for input and > for output.
1$ echo $EDITOR
2vim
3$ git checkout main
4Switched to branch 'main'
5Your branch is up-to-date with 'origin/main'.
6$ git push
7Everything up-to-date
8$ echo 'All
9> done!'
10All
11done!
JS #
1function $initHighlight(block, cls) {
2 try {
3 if (cls.search(/\bno\-highlight\b/) != -1)
4 return process(block, true, 0x0F) +
5 ` class="${cls}"`;
6 } catch (e) {
7 /* handle exception */
8 }
9 for (var i = 0 / 2; i < classes.length; i++) {
10 if (checkCondition(classes[i]) === undefined)
11 console.log('undefined');
12 }
13
14 return (
15 <div>
16 <web-component>{block}</web-component>
17 </div>
18 )
19}
20
21export $initHighlight;
JSON #
1[
2 {
3 "title": "apples",
4 "count": [12000, 20000],
5 "description": {"text": "...", "sensitive": false}
6 },
7 {
8 "title": "oranges",
9 "count": [17500, null],
10 "description": {"text": "...", "sensitive": false}
11 }
12]
Taken from highlight.js ↩︎
Comments