summaryrefslogtreecommitdiff
path: root/lib/rouge/formatters/html_gitlab.rb
blob: 791a95238848502c9887553acb39c210eef1a3f8 (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
require 'cgi'

module Rouge
  module Formatters
    class HTMLGitlab < Rouge::Formatter
      tag 'html_gitlab'

      # Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance.
      #
      # [+cssclass+]        CSS class for the wrapping <tt><div></tt> tag
      #                     (default: 'highlight').
      # [+lineanchors+]     If set to true the formatter will wrap each output
      #                     line in an anchor tag with a name of L-linenumber.
      #                     This allows easy linking to certain lines
      #                     (default: false).
      # [+lineanchorsid+]   If lineanchors is true the name of the anchors can
      #                     be changed with lineanchorsid to e.g. foo-linenumber
      #                     (default: 'L').
      def initialize(
          cssclass: 'highlight',
          lineanchors: false,
          lineanchorsid: 'L'
      )
        @cssclass = cssclass
        @lineanchors = lineanchors
        @lineanchorsid = lineanchorsid
      end

      def render(tokens)
        data = process_tokens(tokens)

        wrap_lines(data[:code])
      end

      alias_method :format, :render

      private

      def process_tokens(tokens)
        rendered = []
        current_line = ''

        tokens.each do |tok, val|
          # In the case of multi-line values (e.g. comments), we need to apply
          # styling to each line since span elements are inline.
          val.lines.each do |line|
            stripped = line.chomp
            current_line << span(tok, stripped)

            if line.end_with?("\n")
              rendered << current_line
              current_line = ''
            end
          end
        end

        # Add leftover text
        rendered << current_line if current_line.present?

        { code: rendered }
      end

      def wrap_lines(lines)
        if @lineanchors
          lines = lines.each_with_index.map do |line, index|
            number = index + @linenostart

            "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \
            '</span>'
          end
        end

        lines.join("\n")
      end

      def span(tok, val)
        # http://stackoverflow.com/a/1600584/2587286
        val = CGI.escapeHTML(val)

        if tok.shortname.empty?
          val
        else
          "<span class=\"#{tok.shortname}\">#{val}</span>"
        end
      end
    end
  end
end