thanpolas // web development as it happens

Setting up my new webpage and blog, thanpol.as, using Jekyll proved to be a bit more cumbersome than expected. Specifically with syntax highlighting. To get it just right, I had to create a custom plugin just to wrap the generated markup in a block container.

As a consequence i had to let go of directly deploying the Jekyll repository to Github pages and resort to pushing the static generated website instead. It strikes me as odd that, what I believed to be a trivial task, proved to be so hard and what I discovered that was required for highlighting to work, was not offered as an option from Jekyll. So finally, i ended up fixing the problem with Javascript.

Syntax Highlighting Options

Although I know there are many solutions for Syntax Highlighting out there, I wanted to go with Jekyll's "native" way, pygments and Liquid Templates. Syntax looked ok, codeblocks are required to be inside {% highlight html %} and {% endhighlight %} tags.

This codeblock will generate pygmented markup like the following Javascript sample:

for (var i = 0, len = ar.length; i < 0; i++) {
  newAr.push(ar[i].shift());
}

Pretty straightforward... Now how about adding some line numbers?

Adding line numbers to codeblocks in Jekyll

Again, it looked simple, just add the key linenos in the liquid tags and line numbers are added!

1
2
3
4
// so this codeblock has been generated
// by adding the property 'linenos' to the
// syntaxhighlight tag
for (var i = 0, len = ar.length; i < 0; i++) {

However, the line numbers are inline with the code which creates a "Select and Copy" problem... You get the line numbers in the clipboard, essentially making the codeblock not copy-pasteable.

The solution for that is to use one additional parameter, the table. So the opening liquid tag would look like this {% highlight language linenos=table %}. Which produces this result:

1
2
3
for (var i = 0, len = ar.length; i < 0; i++) {
  newAr.push(ar[i].shift());
}

So far so good, but what happens at the odd cases where a line might need to wrap?

The problems of Jekyll's Syntax Highlighting

The codeblock is rendered as a table with two columns, the first containing the line numbers and the second the actual code. Each column has only one row, which in its turn has the <pre><code> ... </code></pre> elements. It looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<table class="highlighttable">
  <tr>
    <td class="linenos">
      <div class="linenodiv">
        <pre>
          <code class="javascript"> 1
 2
          </code>
        </pre>
      </div>
    </td>
    <td class="code">
      <div class="highlight"><pre> ... </pre></div>
    </td>
  </tr>
</table>

So, naturally, when a line is exceeding its width and wrapped this is what happens:

Jekyll syntax highlighting with table format and lines wrapping problem

The second column of the table, where the code existed, has a width of 100%. Having a fixed width is out of the question, so there were little to go with for handling this situation.

Rather than trying to somehow properly align the line numbers with the wrapped lines, I decided to force the code to not wrap and have horizontal scroll bars if the text overflowed.

However, with no fixed width on the table cell, there is no proper solution to have horizontal bars for a child element. In the case of a no-wrapping pre things get nasty. Challenge yourself in this Dabblet.

A solution to this problem, is to wrap the table into a containing block. Then the block can have horizontal bars if the table overflowed. And this option, plainly does not exist for Jekyll. The {% highlight %}... tag is Jekyll's native plugin for the liquid templates. It takes the codeblock and runs it through albino, a ruby wrapper for pygmentize, before it is pushed upwards the stack, to whatever engine the document is (md, html, textile).

There is no option or way to define a wrapper for the generated code. There is also no way to wrap around a tag in the liquid templates... This left me with three choices:

  1. Manually wrap codeblocks with the containing block. Very verbose way of expressing, not elegant, turned me down.
  2. Create a custom plugin for Jekyll that would do the job. Loosing the ability to push Jekyll sources to Github for auto-generation.
  3. Wrap the elements with Javascript sensible, not perfect but effective.

I chose to create a custom plugin for Jekyll

I wanted to experiment so I gave it a try. It worked, but not without issues. Essentially I overwrote the highlight tag defined by Jekyll, and wrapped its output with the proper container block:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module Jekyll

  class WrapHighlightBlock < Jekyll::HighlightBlock
    def initialize(tag_name, markup, tokens)
      super
    end
    def render(context)
      '<figure class="code"><figcaption></figcaption>' + super + '</figure>'
    end
  end

end

Liquid::Template.register_tag('highlight', Jekyll::WrapHighlightBlock)

Custom plugins == no Github love

Once you step to the other side, using custom plugins for Jekyll's generation, you loose the option to directly push to Github on the [username].github.com repo.

So I switched the origin of my Jekyll repo to point to, the new "blog repo" and created pushed the _site folder of Jekyll to github as a separate repo.

But, Jekyll had one last trick up its' sleeve to slam me, it completely erased everything in the _site folder before each new file generation, along with the .git folder that kept the repo's actual data (!). Grunt came to the rescue, with the copy task on a grunt watch I was able to copy the newly generated files into a mirror folder that the .git file was preserved.

One small script to commit & push both repos and the saga was finished.

But, I was not satisfied with this setup so I reverted to solution 3 and using Javascript to wrap the codeblocks with the <figure> container.

After loosing so much time with this, I am left wondering at which point I took the wrong turn. I'd love to hear a Jekyll's expert feedback on this, so please share your thoughts.

blog comments powered by Disqus