Class: Abqari::SyntaxHighlighter

Inherits:
Object
  • Object
show all
Defined in:
lib/abqari/syntax_highlighter.rb

Defined Under Namespace

Classes: Stylesheet

Constant Summary collapse

DEFAULT_THEME =
'github'
COMMONMARKER_BLOCK =

CommonMarker 1.x emits two shapes depending on whether render.unsafe is true (it is, in our config — so authors can use raw HTML in markdown). With unsafe, the lang lives on <code class="language-X"> and <pre> carries an inline style=. Without unsafe, lang lived on <pre lang="X">. Both shapes also wrap tokens in inline-style <span> tags from commonmarker's bundled highlighter — which we strip and re-render with Rouge for theme-aware output.

%r{<pre lang="(?<lang>[^"]+)"[^>]*>\s*<code>(?<code>.*?)</code>\s*</pre>}m.freeze
PLAIN_BLOCK =

Permissive: any attributes on

, optional language class on .

%r{<pre[^>]*>\s*<code(?:\s+class="language-(?<lang>[^"]+)")?>(?<code>.*?)</code>\s*</pre>}m.freeze
SPAN_TAGS =
%r{</?span[^>]*>}.freeze

Class Method Summary collapse

Class Method Details

.highlight(lang, code) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/abqari/syntax_highlighter.rb', line 37

def self.highlight(lang, code)
  # Strip any inline <span> tags from commonmarker's pre-highlighting,
  # then decode HTML entities back to source.
  raw = CGI.unescapeHTML(code.gsub(SPAN_TAGS, ''))

  lexer = (lang && Rouge::Lexer.find(lang)) || Rouge::Lexers::PlainText
  formatter = Rouge::Formatters::HTML.new
  highlighted = formatter.format(lexer.lex(raw))
  lang_class = lang ? %( class="language-#{lang}") : ''

  %(<div class="highlight"><pre class="highlight"><code#{lang_class}>#{highlighted}</code></pre></div>)
end

.process(html) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/abqari/syntax_highlighter.rb', line 22

def self.process(html)
  html = html.gsub(COMMONMARKER_BLOCK) { highlight(Regexp.last_match[:lang], Regexp.last_match[:code]) }
  html.gsub(PLAIN_BLOCK) do
    m = Regexp.last_match
    # Skip blocks the first pass already produced — they carry
    # `class="highlight"` on the <pre>. Re-running `highlight` on them
    # would strip the Rouge `<span>`s (losing the highlighting) and
    # unescape entities a second time (`&amp;amp;` → `&amp;` → `&`).
    pre_tag = m[0][/\A<pre[^>]*>/]
    next m[0] if pre_tag&.include?('highlight')

    highlight(m[:lang], m[:code])
  end
end