Syntax Highlighting for Elm
Nov 26, 2015

I wanted the ability to perform syntax highlighting on snippets of code in this blog so I looked for an open source project which would integrate nicely with this app's stack (Rails 4, HAML, Bootstrap 3 plus Markdown for the blog posts) and preferably also be able to highlight Elm code. Elm is a relatively new language that is totally different to what I'm used to (Perl and Ruby) but which has a number of attractive features and has already been used in some parts of this site (notably, for the family tree).

The first solution that Google found was the Ruby gem CodeRay, but it didn't have support for Elm, so that was that. The next thing that cropped up was highlight.js which had support for a lot more languages (including Elm) and an impressive web site, so I decided to try that out. It turned out to be a pretty painless task (it took less time to get it working than it did to write this post).

Here are the steps I followed:

First, I went to the highlight.js download page, picked all the languages I'm ever likely to post about (about a dozen †) and then clicked the download button. This gave me a ZIP file containing some minified JS code (highlight.pack.js) and a set of CSS files from which to choose a preferred styling.

Next, I added highlight.pack.js to app/assets/javascripts/ and a randomly chosen (for now) CSS file (renamed to highlight.css) to app/assets/stylesheets/. Also I updated config/initializers/assets.rb to tell Rails about the two new assets to pre-compile.

Rails.application.config.assets.precompile += %w(highlight.pack.js highlight.css)

Next, I created a partial view containing JQuery code to trigger the highlighter in app/views/utils/_hightlight.js:

$(document).ready(function() {
  $("pre code").each(function(i, block) {
    hljs.highlightBlock(block);
  });
});

and made sure that this, along with the main two highlight.js files, was loaded whenever a blog post was displayed by inserting the following in app/views/blogs/show.html.haml:

- content_for :head do
  = javascript_include_tag "highlight.pack"
  = stylesheet_link_tag "highlight"

- content_for :javascript do
  = render "utils/highlight.js"

Note that this only works because of the code below in a layout file for the <head> portion of every page, which is how I load stuff that isn't going to be shared across all pages.

- if content_for? :head
  = yield :head

- if content_for? :javascript
  = javascript_tag do
    = yield :javascript

Since only the first paragraph of any post is displayed on the blog index page, I decided to limit syntax highlighting to just the show action for individual blog posts.

Next, in order to prevent HAML trying to indent the code inside <pre><code> blocks, I used ~ (instead of =) to render the HTML produced from the Markdown stored in the database. This HTML is returned by the Blog model's story_html method, so part of app/views/blogs/show.html.haml is:

.panel-body
  ~ blog.story_html

The effect of this ~ is to call HAML's find_and_preserve helper which escapes newlines and prevents HAML messing with the contents of whitespace sensitive elements. Here, panel-body is a Bootstrap CSS class for producing the panel effect you can see on this page (and the blog index page). Update: now that I've upgraded to version 4.1 of Bootstrap, it's changed to card-body.

At this stage syntax highlighting was working, all that remained was to choose a preferred styling. I mentioned before that I'd initially chosen one of the highlight.js CSS files at random, so now I tried some others until I got one that I liked the best. This required restarting the development app and reloading the test blog every time the app/assets/stylesheets/hightlight.css file was overwritten. With a predilection for dark themes, the one I chose in the end was ir_dark.css, with far.css, tomorrow-night.css and dark.css close contenders. Update: when I upgraded highlight in July 2018, ir_dark.css didn't exist, but ir-black.css did and it seems to be similar.

Note: as I later found out, the best way to to quickly compare styles is to use the style demo page.

Lastly, I ran all the tests, committed to the local Git repo, pushed to my Bitbucket repo and deployed with Capistrano. And that's all there was to it.

To conclude, here's a demonstration of highlight.js working on a random snippet of Elm code:

textStyle : Text.Style
textStyle =
  { defaultStyle
  | typeface = ["Verdana", "Helvetica", "sans-serif"]
  , height = Just 15
  }

† Bash, CSS, CoffeScript, HTML/XML, JSON, JavaScript, Markdown, Perl, Ruby, SQL, ERB, Elm, HAML, SCSS.

Blog