syntax_highlight_filter.rb 2.01 KB
Newer Older
1
require 'rouge/plugins/common_mark'
2 3
require 'rouge/plugins/redcarpet'

4 5
module Banzai
  module Filter
6 7 8 9 10 11 12 13 14 15 16 17
    # HTML Filter to highlight fenced code blocks
    #
    class SyntaxHighlightFilter < HTML::Pipeline::Filter
      def call
        doc.search('pre > code').each do |node|
          highlight_node(node)
        end

        doc
      end

      def highlight_node(node)
18
        css_classes = 'code highlight js-syntax-highlight'
19 20
        lang = node.attr('lang')
        retried = false
21

22 23
        if use_rouge?(lang)
          lexer = lexer_for(lang)
24
          language = lexer.tag
25 26 27 28 29 30 31 32 33 34 35 36 37 38
        else
          lexer = Rouge::Lexers::PlainText.new
          language = lang
        end

        begin
          code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, node.text), tag: language)
          css_classes << " #{language}" if language
        rescue
          # Gracefully handle syntax highlighter bugs/errors to ensure users can
          # still access an issue/comment/etc. First, retry with the plain text
          # filter. If that fails, then just skip this entirely, but that would
          # be a pretty bad upstream bug.
          return if retried
39

40 41 42
          language = nil
          lexer = Rouge::Lexers::PlainText.new
          retried = true
43

44
          retry
45
        end
46

47
        highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
48

49 50
        # Extracted to a method to measure it
        replace_parent_pre_element(node, highlighted)
51 52 53 54
      end

      private

55 56 57 58 59 60 61 62 63
      # Separate method so it can be instrumented.
      def lex(lexer, code)
        lexer.lex(code)
      end

      def lexer_for(language)
        (Rouge::Lexer.find(language) || Rouge::Lexers::PlainText).new
      end

64 65 66 67
      def replace_parent_pre_element(node, highlighted)
        # Replace the parent `pre` element with the entire highlighted block
        node.parent.replace(highlighted)
      end
68 69 70 71

      def use_rouge?(language)
        %w(math mermaid plantuml).exclude?(language)
      end
72 73 74
    end
  end
end