Hugo Theme (Part 3) : Syntax Highlighting
In this post, we will add syntax highlighting. We will create colors for light and dark mode. We will also add support for sample code output inside code blocks.
Code block
We need to generate color styles for code blocks.
Light mode
hugo gen chromastyles --style=monokailight > themes/jack/assets/css/syntax.css
In the assets/css/syntax.css, surround the generated styles with
:not(.dark) > body { ... }
For dark mode
hugo gen chromastyles --style=monokai >> themes/jack/assets/css/syntax.css
In the assets/css/syntax.css, surround the generated styles with
.dark { ... }
Link the generated file to our template
In layouts/partials/head/css.html
{{- with resources.Get "css/syntax.css" }}
{{- if eq hugo.Environment "development" }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- else }}
{{- with . | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- end }}
{{- end }}
Tailwind prose
class adds some unneeded margins and paddings to code blocks when using highlight
shortcode. Let’s add a quick fix for that
.chroma .lnt, .chroma .hl {
display: flex;
}
.highlight .lntd:first-child > pre {
@apply my-0 pr-0;
}
.highlight .lntd:nth-child(2) > pre {
@apply my-0 pl-0;
}
Inline code block
:not(pre) > code {
@apply bg-white dark:bg-slate-800 italic px-1;
}
Sample output
We can override the default code block renderer with our own to enable support for sample output in code blocks. In the code block we can find all the nested sample code blocks and replace them with their hash before sending them to be rendered by Goldmark renderer. We can then substitute the sample output text back in.
[!Warning]
This code block passes the sample output text through
safeHTML
function which should not be used for content from untrusted sources.
Create layouts/_default/_markup/render-codeblock.html
{{ $class := .Attributes.class | default "" }}
{{ $lang := .Attributes.lang | default .Type }}
{{ $Inner := .Inner }}
{{ if transform.CanHighlight $lang }}
{{/* Find all the samp blocks, capture them and their contents */}}
{{ $blockmap := dict }}
{{ $blocks := findRESubmatch "((\\s|\\S)*?)" $Inner }}
{{ range $blocks }}
{{/* Add all contents and their hashes to dictionary */}}
{{ $content := trim (index . 1) "\n\r" }}
{{ $hash := crypto.FNV32a $content | string }}
{{ $blockmap = $blockmap | merge (dict $hash $content) }}
{{/* Replace all blocks by hashes */}}
{{ $Inner = replace $Inner (index . 0) $hash }}
{{ end }}
{{/* Render the code blocks */}}
{{ $rendered := highlight $Inner $lang }}
{{/* Remove hashes and add original content back */}}
{{ range $hash, $content := $blockmap }}
{{/* {{ $tmp := highlight $content "text" }} */}}
{{ $tmp := partial "samp.html" (dict "content" $content) }}
{{ $rendered = replace $rendered $hash $tmp}}
{{ end }}
<div class="{{ $class }}">{{ $rendered | safeHTML }}</div>
{{else}}
<pre><code class="{{ $class }}">{{.Inner}}</code></pre>
{{end}}
In layouts/partials/samp.html
<samp class="text-gray-500 dark:text-gray-400">{{ .content }}</samp>
Now we can use it in markdown like
```js
console.log("hello")
```samp
hello
```
```
and it will render
console.log("hello")
hello
Thank you for reading. Check out the other parts in the series below.