summaryrefslogtreecommitdiff
path: root/src/_plugins/highlight.rb
blob: d59140cad637f45e31a545dde4620d901d2befbf (plain)
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# frozen_string_literal: true

module Jekyll
  module Tags
    class HighlightBlock < Liquid::Block
      include Liquid::StandardFilters

      def initialize(tag_name, markup, tokens)
        super

        opts = markup.split
        @opts = Hash.new
        @lang = opts.shift.downcase

        opts.each do |opt|
          if opt.include?("=")
            parts = opt.split("=", 2)

            @opts[parts[0]] = parts[1]
          end

          @opts[opt] = true
        end
      end

      def render(context)
        prefix = context["highlighter_prefix"] || ""
        suffix = context["highlighter_suffix"] || ""
        code = super.to_s.gsub(%r!\A(\n|\r)+|(\n|\r)+\z!, "")

        is_safe = !!context.registers[:site].safe

        output =
          case context.registers[:site].highlighter
          when "pygments"
            render_pygments(code, is_safe)
          when "rouge"
            render_rouge(code)
          else
            render_codehighlighter(code)
          end

        rendered_output = add_code_tag(output)
        prefix + rendered_output + suffix
      end

      def sanitized_opts(opts, is_safe)
        if is_safe
          Hash[[
            [:startinline, opts.fetch(:startinline, nil)],
            [:hl_lines,    opts.fetch(:hl_lines, nil)],
            [:linenos,     opts.fetch(:linenos, nil)],
            [:encoding,    opts.fetch(:encoding, "utf-8")],
            [:cssclass,    opts.fetch(:cssclass, nil)],
          ].reject { |f| f.last.nil? }]
        else
          opts
        end
      end

      private

      def render_pygments(code, is_safe)
        Jekyll::External.require_with_graceful_fail("pygments") unless defined?(Pygments)

        highlighted_code = Pygments.highlight(
          code,
          :lexer   => @lang,
          :options => sanitized_opts(@highlight_options, is_safe)
        )

        if highlighted_code.nil?
          Jekyll.logger.error <<~MSG
            There was an error highlighting your code:

            #{code}

            While attempting to convert the above code, Pygments.rb returned an unacceptable value.
            This is usually a timeout problem solved by running `jekyll build` again.
          MSG
          raise ArgumentError, "Pygments.rb returned an unacceptable value "\
          "when attempting to highlight some code."
        end

        highlighted_code.sub('<div class="highlight"><pre>', "").sub("</pre></div>", "")
      end

      def render_rouge(code)
        require "rouge"
        formatter = ::Rouge::Formatters::HTMLLegacy.new(
          :line_numbers => @highlight_options[:linenos],
          :wrap         => false,
          :css_class    => "highlight",
          :gutter_class => "gutter",
          :code_class   => "code"
        )
        lexer = ::Rouge::Lexer.find_fancy(@lang, code) || Rouge::Lexers::PlainText
        formatter.format(lexer.lex(code))
      end

      def render_codehighlighter(code)
        h(code).strip
      end

      def add_code_tag(code)
        code_attributes = [
          "class=\"language-#{@lang.to_s.tr("+", "-")}\"",
          "data-lang=\"#{@lang}\"",
        ].join(" ")

        output = "<figure class=\"highlight\"><pre><code #{code_attributes}>"
        output += code.chomp
        output += "</code></pre>"

        if @opts["tio"]
          output += "<div class=\"code-link\"><a href=\"#{@opts["tio"]}\" target=\"_blank\">Run this code</a></div>"
        end

        output += "</figure>"

        output
      end
    end
  end
end

Liquid::Template.register_tag("highlight", Jekyll::Tags::HighlightBlock)