On Ruby on Rails, how to do Syntax highlighting using Markdown (RDiscount / BlueCloth) with CodeRay?

1.3k Views Asked by At

I am aware there is a Railscast and ASCIIcast for using Textile (RedCloth) with CodeRay, and the way it is done is by a helper:

module ApplicationHelper    
  def coderay(text)  
    text.gsub(/\<code( lang="(.+?)")?\>(.+?)\<\/code\>/m) do  
      CodeRay.scan($3, $2).div(:css => :class)  
    end  
  end  
end  

and

<%= textilize(coderay(@article.content)) %>

so the assumption is that the @article.content already has <code> and </code> wrapped around (so that CodeRay can work)...

but what if it is Markdown, then the "4 space indentation", like on StackOverflow, will first need to be converted to <code> and </code> format first.

So in this case, seems like we can use

<%= coderay(Markdown.new(@article.content).to_html).html_safe  #html_safe for Rails 3 %>  

so it first gets the <code> and </code> format for CodeRay to use, and then just basically substitute /<code>.*?</code>/m with the CodeRay results.

Is this a proper way to do it? But if what we actually have <code> and </code> on the "4 space indented" code, then it will actually choke this processing, because now there is nested <code>, so the first <code> will get matched, skipping the second <code> as just content for CodeRay, and then match the first </code> and leave the second </code> dangling there unprocessed. How can this be done -- maybe CodeRay has some Markdown options?

1

There are 1 best solutions below

0
On

There is a tiny gem called lesstile that helps with this. I didn't feel like using the gem though, so I extracted the core functionality, which boils down to this:

options = {
    :code_formatter => lambda {|code, lang|
        CodeRay.scan(CGI::unescapeHTML(code), lang).html.div
    }
}

text += "\n" unless (text.rindex("\n") == text.size - 1)
text.gsub!(/\r\n/, "\n")
output = ""

while match = text.match(/---\s*?([\w\s\._+()-]*?)\s*?\n(.*?)---\n/m)
    captures = match.captures
    code = captures[1]
    lang = captures[0].blank? ? nil : captures[0].downcase.strip.intern

    output += 
    options[:text_formatter][match.pre_match] +
    options[:code_formatter][code, lang]

    text = match.post_match
end

output += options[:text_formatter][text.chomp]

You can denote code in your textile/markdown by using "---" to encase it and optionally add the language, like "---ruby", like this:

---ruby
def hi
  print "hi"
end
---

Works perfectly fine with Markdown.