GitHub-style syntax highlighting with Pygments

Update 2016: This tutorial is out-of-date. See the repository for alexpeattie.com and the associated README to see how I do it now.

A few people have asked me how I do the Github-style syntax highlighting on this site. Here's an example Ruby script with highlighting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A < B; def self.create(object = User) object end end
class Zebra; def inspect; "X#{2 + self.object_id}" end end

module ABC::DEF
  include Comparable

  # @param test
  # @return [String] nothing
  def foo(test)
    Thread.new do |blockvar|
      ABC::DEF.reverse(:a_symbol, :'a symbol', :<=>, 'test' + test)
    end.join
  end

  def [](index) self[index] end
  def ==(other) other == self end
end

anIdentifier = an_identifier
Constant = 1
render action: :new

This how-to explains my setup on Middleman (which this site is built on), but it should be applicable to any site using Pygments for syntax highlighting.

To enable basic syntax highlighting, we need to add the middleman-syntax gem to our Gemfile:

1
gem "middleman-syntax"

and activate it in our config.rb:

1
activate :syntax

We'll also turn on the lineanchors option, which we'll need for line numbering (see below).

1
activate :syntax, lineanchors: 'line'

Fenced code blocks

The first feature we'll add is fenced code blocks, a feature of Github Flavored Markdown. This allows us to conveniently specify the language (and thus, the Pygments lexer) of our code:

1
2
3
~~~ruby
puts "Hello world from ruby"
~~~

We'll need to change our Markdown pre-processor to Redcarpet which supports fenced code blocks. We need to add:

1
gem "redcarpet"

to our Gemfile and put the following into our config.rb:

1
2
set :markdown_engine, :redcarpet
set :markdown, fenced_code_blocks: true

Github highlighting color scheme

Next we'll add some CSS to match Github's color scheme: https://github.com/richleland/pygments-css/blob/master/github.css

We have to make one small change, .hll needs to be replaced with .highlight

1
2
.hll { background-color: #ffffcc } /* WRONG */
.highlight { background-color: #ffffcc } /* RIGHT */

Line numbers

The last thing we're missing are line-numbers. We'll implement this in pure CSS, using CSS counters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
pre {
    counter-reset: line-numbering;
    border: solid 1px #d9d9d9;
    border-radius: 0;
    background: #fff;
    padding: 0;
    line-height: 23px;
    margin-bottom: 30px;
    white-space: pre;
    overflow-x: auto;
    word-break: inherit;
    word-wrap: inherit;
}

pre a::before {
  content: counter(line-numbering);
  counter-increment: line-numbering;
  padding-right: 1em; /* space after numbers */
  width: 25px;
  text-align: right;
  opacity: 0.7;
  display: inline-block;
  color: #aaa;
  background: #eee;
  margin-right: 16px;
  padding: 2px 10px;
  font-size: 13px;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

pre a:first-of-type::before {
  padding-top: 10px;
}

pre a:last-of-type::before {
  padding-bottom: 10px;
}

pre a:only-of-type::before {
  padding: 10px;
}

Notice the user-select: none; - this ensures that when the code is selected, the line numbers won't be selected too, which makes copying and pasting a lot more convenient.

CSS counters will work fine for ~95% of users - and older browsers will degrade gracefully (they just won't show any line numbers).