GitHub style Pygments syntax highlighting in Jade
Since I started blogging a few months ago, I fought with syntax highlighting on every step. I played around with manually highlighting my code for best results, but I very soon realized I simply do not have the patience. The need for a syntax highlighter was born. I believe in rendering as much as possible just once and server side. After some poking around I went with the Pygments syntax highlighter. If it's good enough for GitHub, it must be good enough for me...
My original idea was to create a custom filter for Jade
that would accept the language as an argument.
This proved unpractical since Jade filters don't support arguments
and it still left the markdown fenced code blocks plain.
Thus an easier solution was born.
Highlight the fenced blocks and use the :markdown
filter.
The code
Jade lets you specify (override) the markdown parser easily
and the marked
parser supports fenced code blocks,
but a bit of tinkering is still needed to add the Pygments.
jade = require 'jade'
marked = require 'marked'
{execSync} = require 'child_process' # node >= 0.11.12
renderer = new marked.Renderer
renderer.code = (code, lexer = 'text') ->
result = execSync "pygmentize -l #{lexer} -f html",
input: code
return result.toString()
marked.setOptions
renderer: renderer
jade.filters.markdown = marked
marked
exposes the renderer.code
function
that handles the markdown code blocks.
The default implementation calls renderer.options.highlight
if the fenced code block specifies a language,
but it also (correctly) wraps the result in <pre><code>
.
Since the Pygments highlighter
already wraps its output in <div class="highlight"><pre>
,
it is not enough to override the renderer.options.highlight
.
We must override the renderer.code
function instead
to get rid of the clutter.
To actually highlight the code, I use the pygmentize
command line utility.
Since Jade doesn't support async filters,
we are limited to synchronous calls
and must use the child_process.execSync
to call pygmentize
.
With marked.setOptions
we override the renderer to our custom one
and then override the Jade markdown filter
to use our customized marked
instance.
Usage
Whenever you need a highlighted code block in jade,
use the :markdown
filter with a fenced code block.
Highlighting CoffeeScript
*:
:markdown
```coffee-script
foods = ['broccoli', 'spinach', 'chocolate']
eat food for food in foods when food isnt 'chocolate'
```
foods = ['broccoli', 'spinach', 'chocolate']
eat food for food in foods when food isnt 'chocolate'
<div class="highlight"><pre><span class="nv">foods = </span><span class="p">[</span><span class="s">'broccoli'</span><span class="p">,</span> <span class="s">'spinach'</span><span class="p">,</span> <span class="s">'chocolate'</span><span class="p">]</span>
<span class="nx">eat</span> <span class="nx">food</span> <span class="k">for</span> <span class="nx">food</span> <span class="nx">infoods</span> <span class="nx">whenfoodisnt</span> <span class="s">'chocolate'</span>
</pre></div>
Highlighting Bash
:
:markdown
```bash
if test -r "${HOME}/.profile"; then
source "${HOME}/.profile"
fi
```
if test -r "${HOME}/.profile"; then
source "${HOME}/.profile"
fi
<div class="highlight"><pre><span class="k">if </span><span class="nb">test</span> -r <span class="s2">"${HOME}/.profile"</span>; <span class="k">then</span>
<span class="k"> </span><span class="nb">source</span> <span class="s2">"${HOME}/.profile"</span>
<span class="k">fi</span>
</pre></div>