forked from asciidoctor/asciidoctor-pdf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resolves asciidoctor#247 add support for the Rouge source highlighter
- add integration with Rouge for highlighting source listings - use no-break space for indent guard instead of zero-width space - guard indentation within Rouge formatter - organize the code to setup source highlighting - enable line number support when highlighting with Rouge - add pastie theme for Rouge - patch Rouge style lookup (see rouge-ruby/rouge#280) - cast lookup collections to set - optimize code
- Loading branch information
1 parent
68b4fb5
commit 24b3950
Showing
6 changed files
with
261 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
require 'rouge' | ||
require_relative 'rouge_ext/formatters/prawn' | ||
require_relative 'rouge_ext/css_theme' | ||
require_relative 'rouge_ext/themes/pastie' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module Rouge | ||
class CSSTheme | ||
# Patch style_for to return most specific style first | ||
# See https://github.com/jneen/rouge/issues/280 (fix pending) | ||
def style_for token | ||
token.token_chain.reverse_each do |t| | ||
if (s = styles[t]) | ||
return s | ||
end | ||
end | ||
nil | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
module Rouge | ||
module Formatters | ||
# Transforms a token stream into an array of | ||
# formatted text fragments for use with Prawn. | ||
class Prawn < Formatter | ||
tag 'prawn' | ||
|
||
EOL = %(\n) | ||
NoBreakSpace = %(\u00a0) | ||
InnerIndent = %(\n ) | ||
GuardedIndent = %(\u00a0) | ||
GuardedInnerIndent = %(\n\u00a0) | ||
BoldStyle = [:bold].to_set | ||
ItalicStyle = [:italic].to_set | ||
BoldItalicStyle = [:bold, :italic].to_set | ||
|
||
def initialize opts = {} | ||
unless ::Rouge::Theme === (theme = opts[:theme]) | ||
unless theme && (theme = ::Rouge::Theme.find theme) | ||
theme = ::Rouge::Themes::Pastie | ||
end | ||
theme = theme.new | ||
end | ||
@theme = theme | ||
@normalized_colors = {} | ||
@linenum_fragment_base = (create_fragment Token['Generic.Lineno']).merge linenum: true | ||
end | ||
|
||
# Override format method so fragments don't get flatted to a string | ||
# and to add an options Hash. | ||
def format tokens, opts = {} | ||
stream tokens, opts | ||
end | ||
|
||
def stream tokens, opts = {} | ||
if opts[:line_numbers] | ||
# TODO implement line number start (offset) | ||
linenum = 0 | ||
fragments = [] | ||
fragments << (create_linenum_fragment linenum += 1) | ||
tokens.each do |tok, val| | ||
fragment = create_fragment tok, val | ||
if val == EOL | ||
fragments << fragment | ||
fragments << (create_linenum_fragment linenum += 1) | ||
elsif val.include? EOL | ||
val.each_line do |line| | ||
fragments << (fragment.merge text: line) | ||
# NOTE append linenum fragment if there's a next line; only works if source doesn't have trailing endline | ||
if line.end_with? EOL | ||
fragments << (create_linenum_fragment linenum += 1) | ||
end | ||
end | ||
else | ||
fragments << fragment | ||
end | ||
end | ||
# NOTE drop orphaned linenum fragment (due to trailing endline in source) | ||
fragments.pop if (last_fragment = fragments[-1]) && last_fragment[:linenum] | ||
# NOTE pad numbers with less digits than the highest line number | ||
if (linenum_w = (linenum / 10) + 1) > 1 | ||
# NOTE extra column is the trailing space after the line number | ||
linenum_w += 1 | ||
fragments.each do |fragment| | ||
fragment[:text] = %(#{fragment[:text].rjust linenum_w, NoBreakSpace}) if fragment[:linenum] | ||
end | ||
end | ||
fragments | ||
else | ||
start_of_line = true | ||
tokens.map do |tok, val| | ||
# match one or more consecutive endlines | ||
if val == EOL || (val == (EOL * val.length)) | ||
start_of_line = true | ||
{ text: val } | ||
else | ||
val[0] = GuardedIndent if start_of_line && (val.start_with? ' ') | ||
val.gsub! InnerIndent, GuardedInnerIndent if val.include? InnerIndent | ||
start_of_line = val.end_with? EOL | ||
# NOTE this optimization assumes we don't support/use background colors | ||
val.rstrip.empty? ? { text: val } : (create_fragment tok, val) | ||
end | ||
end | ||
# QUESTION should we strip trailing newline? | ||
end | ||
end | ||
|
||
# TODO method could still be optimized (for instance, check if val is EOL) | ||
def create_fragment tok, val = nil | ||
fragment = val ? { text: val } : {} | ||
if (style_rules = @theme.style_for tok) | ||
# TODO support background color | ||
if (fg = normalize_color style_rules.fg) | ||
fragment[:color] = fg | ||
end | ||
if style_rules[:bold] | ||
fragment[:styles] = style_rules[:italic] ? BoldItalicStyle : BoldStyle | ||
elsif style_rules[:italic] | ||
fragment[:styles] = ItalicStyle | ||
end | ||
end | ||
fragment | ||
end | ||
|
||
def create_linenum_fragment linenum | ||
@linenum_fragment_base.merge text: %(#{linenum} ) | ||
end | ||
|
||
def normalize_color raw | ||
return unless raw | ||
if (normalized = @normalized_colors[raw]) | ||
normalized | ||
else | ||
normalized = (raw.start_with? '#') ? raw[1..-1] : raw | ||
normalized = normalized.each_char.map {|c| c * 2 }.join if normalized.size == 3 | ||
@normalized_colors[raw] = normalized | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.