main
rb 41 lines 1.24 KB
Raw
1 require "rouge"
2 require "cgi"
3
4 # Server-side syntax highlighting for the blob view. Returns one HTML fragment
5 # per source line so each line can be numbered and individually anchored.
6 #
7 # The whole file is tokenized in a single pass (so stateful constructs such as
8 # heredocs and multi-line strings highlight correctly), then the token stream
9 # is split on newline boundaries. Unknown languages fall back to a plain-text
10 # lexer; any lexer error falls back to HTML-escaped plain lines.
11 class SyntaxHighlighter
12 def self.lines(content, filename:)
13 new(content, filename: filename).lines
14 end
15
16 def initialize(content, filename:)
17 @content = content
18 @filename = filename
19 end
20
21 def lines
22 lexer = Rouge::Lexer.guess(filename: @filename, source: @content) || Rouge::Lexers::PlainText
23 formatter = Rouge::Formatters::HTML.new
24
25 rows = [ [] ]
26 lexer.lex(@content).each do |token, value|
27 value.split(/(\n)/, -1).each do |part|
28 if part == "\n"
29 rows << []
30 elsif !part.empty?
31 rows.last << [ token, part ]
32 end
33 end
34 end
35 rows.pop if rows.last&.empty?
36
37 rows.map { |tokens| formatter.format(tokens) }
38 rescue StandardError
39 @content.lines.map { |l| CGI.escapeHTML(l.chomp) }
40 end
41 end