summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Allen <dan.j.allen@gmail.com>2019-02-03 14:20:00 -0700
committerGitHub <noreply@github.com>2019-02-03 14:20:00 -0700
commitcb16fea43fdec80569c34fd34acdcda65e2c71a1 (patch)
tree4871b62af6fc8f41f3a161b43f6c7c0ea10e9bca
parentf8a490063bc45190458678bc492ab3a1c5b321b3 (diff)
resolves #1040 add syntax highlighter adapter for Rouge (PR #3033)
- add SyntaxHighlighter adapter implementation for Rouge - support CGI options attached to language (e.g., "console?prompt=$ ") - change CSS class of line numbering table for Pygments and Rouge to linenotable - reuse styles from default stylesheet to layout the line number column - remove nested pre wrapper in Pygments output when line numbering is enabled and linenums mode is inline - ensure syntax highlighter library is loaded when looking up base style - add the rouge gem as a development dependency
-rw-r--r--CHANGELOG.adoc2
-rw-r--r--asciidoctor.gemspec2
-rw-r--r--data/stylesheets/asciidoctor-default.css11
-rw-r--r--lib/asciidoctor/rouge_ext.rb38
-rw-r--r--lib/asciidoctor/syntax_highlighter.rb8
-rw-r--r--lib/asciidoctor/syntax_highlighter/pygments.rb16
-rw-r--r--lib/asciidoctor/syntax_highlighter/rouge.rb126
-rw-r--r--test/syntax_highlighter_test.rb264
8 files changed, 445 insertions, 22 deletions
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index c84d25a8..41749f17 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -20,6 +20,7 @@ Enhancements::
* drop support for Ruby < 2.3 and JRuby < 9.1 and remove workarounds (#2764)
* drop support for Slim < 3 (#2998)
* make syntax highlighter pluggable; extract all logic into adapter classes (#2106)
+ * add syntax highlighter adapter for Rouge (#1040)
* add support for start attribute when using prettify to highlight source blocks with line numbering enabled
* use String#encode to encode String as UTF-8 instead of using String#force_encoding (#2764)
* add FILE_READ_MODE, URI_READ_MODE, and FILE_WRITE_MODE constants to control open mode when reading files and URIs and writing files (#2764)
@@ -41,6 +42,7 @@ Improvements::
* value comparison in AbstractNode#attr? is only performed if expected value is truthy
* align default CodeRay style with style for other syntax highlighters (#2106)
* ensure linenos class is added to linenos column when source highlighter is pygments and pygments-css=style
+ * rename CSS class of Pygments line numbering table to linenotable (to align with Rouge) (#1040)
Bug Fixes::
diff --git a/asciidoctor.gemspec b/asciidoctor.gemspec
index 51c4e6c7..bfa11491 100644
--- a/asciidoctor.gemspec
+++ b/asciidoctor.gemspec
@@ -51,6 +51,8 @@ Gem::Specification.new do |s|
s.add_development_dependency 'minitest', '~> 5.11.0'
s.add_development_dependency 'nokogiri', '~> 1.10.0'
s.add_development_dependency 'rake', '~> 12.3.0'
+ # Asciidoctor supports Rouge >= 3
+ s.add_development_dependency 'rouge', '~> 3.3.0'
s.add_development_dependency 'rspec-expectations', '~> 3.8.0'
s.add_development_dependency 'slim', '~> 4.0.0'
s.add_development_dependency 'tilt', '~> 2.0.0'
diff --git a/data/stylesheets/asciidoctor-default.css b/data/stylesheets/asciidoctor-default.css
index d0b21e57..a9f91bf1 100644
--- a/data/stylesheets/asciidoctor-default.css
+++ b/data/stylesheets/asciidoctor-default.css
@@ -223,13 +223,12 @@ pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
pre.prettyprint li code[data-lang]::before{opacity:1}
pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
-table.pygments-table{border-collapse:separate;border:0;margin-bottom:0;background:none}
-table.pygments-table td{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
-table.pygments-table td.code{padding-left:.75em}
-pre.pygments .lineno,table.pygments-table td.linenos{border-right:1px solid currentColor;opacity:.35}
-pre.pygments .lineno{display:inline-block;margin-right:.75em}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos{border-right:1px solid currentColor;opacity:.35;padding-right:.5em}
+pre.pygments .lineno{border-right:1px solid currentColor;opacity:.35;display:inline-block;margin-right:.75em}
pre.pygments .lineno::before{content:"";margin-right:-.125em}
-table.pygments-table td.linenos{padding-right:.5em}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
diff --git a/lib/asciidoctor/rouge_ext.rb b/lib/asciidoctor/rouge_ext.rb
new file mode 100644
index 00000000..a4ed621a
--- /dev/null
+++ b/lib/asciidoctor/rouge_ext.rb
@@ -0,0 +1,38 @@
+require 'rouge'
+
+module Asciidoctor; module RougeExt; module Formatters
+ class HTMLTable < ::Rouge::Formatter
+ def initialize delegate, opts
+ @delegate = delegate
+ @start_line = opts[:start_line] || 1
+ end
+
+ def stream tokens
+ formatted_code = @delegate.format tokens
+ formatted_code += LF unless formatted_code.end_with? LF, HangingEndSpanTagCs
+ last_lineno = (first_lineno = @start_line) + (formatted_code.count LF) - 1 # assume number of newlines is constant
+ lineno_format = %(%#{(::Math.log10 last_lineno).floor + 1}i)
+ formatted_linenos = ((first_lineno..last_lineno).map {|lineno| sprintf lineno_format, lineno } << '').join LF
+ yield %(<table class="linenotable"><tbody><tr><td class="linenos gl"><pre class="lineno">#{formatted_linenos}</pre></td><td class="code"><pre>#{formatted_code}</pre></td></tr></tbody></table>)
+ end
+ end
+
+ class HTMLLineHighlighter < ::Rouge::Formatter
+ def initialize delegate, opts
+ @delegate = delegate
+ @lines = opts[:lines] || []
+ end
+
+ def stream tokens
+ lineno = 0
+ token_lines tokens do |tokens_in_line|
+ yield (@lines.include? lineno += 1) ? %(<span class="hll">#{@delegate.format tokens_in_line}#{LF}</span>) : %(#{@delegate.format tokens_in_line}#{LF})
+ end
+ end
+ end
+
+ HangingEndSpanTagCs = %(\n</span>)
+ LF = ?\n
+
+ private_constant :HangingEndSpanTagCs, :LF
+end; end; end
diff --git a/lib/asciidoctor/syntax_highlighter.rb b/lib/asciidoctor/syntax_highlighter.rb
index 81e2d206..dd1c287a 100644
--- a/lib/asciidoctor/syntax_highlighter.rb
+++ b/lib/asciidoctor/syntax_highlighter.rb
@@ -8,7 +8,7 @@ module Asciidoctor
# that returns true. The companion docinfo method will then be called to insert markup into the output document. The
# docinfo functionality is available to both adapter types.
#
-# Asciidoctor provides several built-in adapters, including coderay, pygments, highlight.js, html-pipeline, and
+# Asciidoctor provides several built-in adapters, including coderay, pygments, rouge, highlight.js, html-pipeline, and
# prettify. Additional adapters can be registered using SyntaxHighlighter.register or by supplying a custom factory.
module SyntaxHighlighter
# Public: Returns the String name of this syntax highlighter for referencing it in messages and option names.
@@ -175,9 +175,8 @@ module SyntaxHighlighter
@@mutex.synchronize { register syntax_highlighter, *names }
end
- # In addition to retrieving the syntax highlighter class or object registered for the specified name, this
- # method will lazy require and register additional built-in implementations (coderay, pygments, and prettify).
- # Refer to {Factory#for} for parameters and return value.
+ # This method will lazy require and register additional built-in implementations, which include coderay,
+ # pygments, rouge, and prettify. Refer to {Factory#for} for parameters and return value.
def for name
@@registry.fetch name do
@@mutex.synchronize do
@@ -198,6 +197,7 @@ module SyntaxHighlighter
'coderay' => %(#{__dir__}/syntax_highlighter/coderay),
'prettify' => %(#{__dir__}/syntax_highlighter/prettify),
'pygments' => %(#{__dir__}/syntax_highlighter/pygments),
+ 'rouge' => %(#{__dir__}/syntax_highlighter/rouge),
}
private
diff --git a/lib/asciidoctor/syntax_highlighter/pygments.rb b/lib/asciidoctor/syntax_highlighter/pygments.rb
index d9feca7c..b1adaca3 100644
--- a/lib/asciidoctor/syntax_highlighter/pygments.rb
+++ b/lib/asciidoctor/syntax_highlighter/pygments.rb
@@ -36,12 +36,8 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
node.sub_specialchars source # handles nil response from ::Pygments::Lexer#highlight
end
elsif (highlighted = lexer.highlight source, options: highlight_opts)
- if linenos
- highlighted = highlighted.gsub StyledLinenoSpanTagRx, LinenoSpanTagCs if noclasses
- highlighted.sub WrapperTagRx, PreTagCs
- else
- highlighted.sub WrapperTagRx, '\1'
- end
+ highlighted = highlighted.gsub StyledLinenoSpanTagRx, LinenoSpanTagCs if linenos && noclasses
+ highlighted.sub WrapperTagRx, '\1'
else
node.sub_specialchars source # handles nil response from ::Pygments::Lexer#highlight
end
@@ -103,7 +99,7 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
private
def base_style style
- @@base_style_cache[style || DEFAULT_STYLE]
+ library_available? ? @@base_style_cache[style || DEFAULT_STYLE] : nil
end
def style_available? style
@@ -141,8 +137,10 @@ class SyntaxHighlighter::PygmentsAdapter < SyntaxHighlighter::Base
PreTagCs = '<pre>\1</pre>'
StyledLinenoColumnStartTagsRx = /<td><div class="linenodiv" style="[^"]+?"><pre style="[^"]+?">/
StyledLinenoSpanTagRx = %r(<span style="background-color: #f0f0f0; padding: 0 5px 0 5px">( *\d+ )</span>)
- WRAPPER_CLASS = 'pygments-'
- # NOTE <pre> has style attribute when pygments-css=style; <div> has trailing newline when pygments-linenums-mode=table; initial <span></span> preserves leading blank lines
+ WRAPPER_CLASS = 'lineno' # doesn't appear in output
+ # NOTE <pre> has style attribute when pygments-css=style
+ # NOTE <div> has trailing newline when pygments-linenums-mode=table
+ # NOTE initial <span></span> preserves leading blank lines
WrapperTagRx = %r(<div class="#{WRAPPER_CLASS}"><pre\b[^>]*?>(.*)</pre></div>\n*)m
private_constant :CodeCellStartTagCs, :LinenoColumnStartTagsCs, :LinenoSpanTagCs, :PreTagCs, :StyledLinenoColumnStartTagsRx, :StyledLinenoSpanTagRx, :WrapperTagRx, :WRAPPER_CLASS
diff --git a/lib/asciidoctor/syntax_highlighter/rouge.rb b/lib/asciidoctor/syntax_highlighter/rouge.rb
new file mode 100644
index 00000000..c78bc357
--- /dev/null
+++ b/lib/asciidoctor/syntax_highlighter/rouge.rb
@@ -0,0 +1,126 @@
+module Asciidoctor
+class SyntaxHighlighter::RougeAdapter < SyntaxHighlighter::Base
+ register_for 'rouge'
+
+ def initialize *args
+ super
+ @requires_stylesheet = nil
+ @style = nil
+ end
+
+ def highlight?
+ library_available?
+ end
+
+ def highlight node, source, lang, opts
+ lexer = (::Rouge::Lexer.find_fancy lang) || ::Rouge::Lexers::PlainText
+ lexer_opts = lexer.tag == 'php' && !(node.option? 'mixed') ? { start_inline: true } : {}
+ @style ||= (style = opts[:style]) && (style_available? style) || DEFAULT_STYLE
+ if opts[:css_mode] == :class
+ @requires_stylesheet = true
+ formatter = ::Rouge::Formatters::HTML.new @style
+ else
+ formatter = ::Rouge::Formatters::HTMLInline.new @style
+ end
+ if (highlight_lines = opts[:highlight_lines])
+ formatter = RougeExt::Formatters::HTMLLineHighlighter.new formatter, lines: highlight_lines
+ end
+ if opts[:line_numbers]
+ formatter = RougeExt::Formatters::HTMLTable.new formatter, start_line: opts[:start_line_number]
+ if opts[:callouts]
+ highlighted = formatter.format lexer.lex source, lexer_opts
+ return [highlighted, (idx = highlighted.index CodeCellStartTagCs) ? idx + CodeCellStartTagCs.length : nil]
+ end
+ end
+ formatter.format lexer.lex source, lexer_opts
+ end
+
+ def format node, lang, opts
+ lang = (lang.split '?', 2)[0] if lang.include? '?'
+ if opts[:css_mode] != :class && (@style = (style = opts[:style]) && (style_available? style) || DEFAULT_STYLE) &&
+ (pre_style_attr_val = base_style @style)
+ opts[:transform] = -> pre, _ { pre['style'] = pre_style_attr_val }
+ end
+ super
+ end
+
+ def docinfo? location
+ @requires_stylesheet && location == :footer
+ end
+
+ def docinfo location, doc, opts
+ if opts[:linkcss]
+ %(<link rel="stylesheet" href="#{doc.normalize_web_path (stylesheet_basename @style), (doc.attr 'stylesdir', ''), false}"#{opts[:self_closing_tag_slash]}>)
+ else
+ %(<style>
+#{read_stylesheet @style}
+</style>)
+ end
+ end
+
+ def write_stylesheet? doc
+ @requires_stylesheet
+ end
+
+ def write_stylesheet doc, to_dir
+ ::File.write (::File.join to_dir, (stylesheet_basename @style)), (read_stylesheet @style), mode: FILE_WRITE_MODE
+ end
+
+ module Loader
+ private
+
+ def library_available?
+ (@@library_status ||= load_library) == :loaded ? true : nil
+ end
+
+ def load_library
+ (defined? ::Rouge::Lexer) ? :loaded : (Helpers.require_library %(#{::File.dirname __dir__}/rouge_ext), 'rouge', :warn).nil? ? :unavailable : :loaded
+ end
+ end
+
+ module Styles
+ include Loader
+
+ def read_stylesheet style
+ library_available? ? @@stylesheet_cache[style || DEFAULT_STYLE] : '/* Rouge CSS disabled because Rouge is not available. */'
+ end
+
+ def stylesheet_basename style
+ %(rouge-#{style || DEFAULT_STYLE}.css)
+ end
+
+ private
+
+ def base_style style
+ library_available? ? @@base_style_cache[style || DEFAULT_STYLE] : nil
+ end
+
+ def style_available? style
+ (::Rouge::Theme.find style) && style
+ end
+
+ (@@base_style_cache = {}).default_proc = proc do |cache, key|
+ base_style = (theme = ::Rouge::Theme.find key).base_style
+ (val = base_style[:fg]) && ((style ||= []) << %(color: #{theme.palette val}))
+ (val = base_style[:bg]) && ((style ||= []) << %(background-color: #{theme.palette val}))
+ @@base_style_cache = cache.merge key => (resolved_base_style = style && (style.join ';'))
+ resolved_base_style
+ end
+ (@@stylesheet_cache = {}).default_proc = proc do |cache, key|
+ @@stylesheet_cache = cache.merge key => (stylesheet = ((::Rouge::Theme.find key).render scope: BASE_SELECTOR))
+ stylesheet
+ end
+
+ DEFAULT_STYLE = 'github'
+ BASE_SELECTOR = 'pre.rouge'
+
+ private_constant :BASE_SELECTOR
+ end
+
+ include Loader, Styles # adds methods to instance
+
+ CodeCellStartTagCs = '<td class="code">'
+
+ private_constant :CodeCellStartTagCs
+end
+end
diff --git a/test/syntax_highlighter_test.rb b/test/syntax_highlighter_test.rb
index 9d972c43..8e2c85fb 100644
--- a/test/syntax_highlighter_test.rb
+++ b/test/syntax_highlighter_test.rb
@@ -586,8 +586,223 @@ context 'Syntax Highlighter' do
end
end
+ context 'Rouge' do
+ test 'should syntax highlight source if source-highlighter attribute is set' do
+ input = <<~'EOS'
+ :source-highlighter: rouge
+
+ [source,ruby]
+ ----
+ require 'rouge'
+
+ html = Rouge::Formatters::HTML.new.format(Rouge::Lexers::Ruby.new.lex('puts "Hello, world!"'))
+ ----
+ EOS
+ output = convert_string input, safe: :safe, linkcss_default: true
+ assert_xpath '//pre[@class="rouge highlight"]/code[@data-lang="ruby"]/span[@class="no"][text()="Rouge"]', output, 2
+ assert_includes output, 'pre.rouge .no {'
+ end
+
+ test 'should default to plain text lexer if lexer cannot be resolved for language' do
+ input = <<~'EOS'
+ :source-highlighter: rouge
+
+ [source,lolcode]
+ ----
+ CAN HAS STDIO?
+ PLZ OPEN FILE "LOLCATS.TXT"?
+ KTHXBYE
+ ----
+ EOS
+ output = convert_string_to_embedded input, safe: :safe
+ assert_css 'code[data-lang=lolcode]', output, 1
+ assert_css 'code span', output, 0
+ assert_xpath %(//code[text()='CAN HAS STDIO?\nPLZ OPEN FILE "LOLCATS.TXT"?\nKTHXBYE']), output, 1
+ end
+
+ test 'should honor cgi-style options on language' do
+ input = <<~'EOS'
+ :source-highlighter: rouge
+
+ [source,"console?prompt=$> "]
+ ----
+ $> asciidoctor --version
+ ----
+ EOS
+ output = convert_string_to_embedded input, safe: :safe
+ assert_css 'code[data-lang=console]', output, 1
+ assert_css 'code span.gp', output, 1
+ end
+
+ test 'should set starting line number in HTML output if linenums option is enabled and start attribute is set' do
+ input = <<~'EOS'
+ [source%linenums,ruby,start=9]
+ ----
+ puts 'Hello, World!'
+ puts 'Goodbye, World!'
+ ----
+ EOS
+ output = convert_string_to_embedded input, attributes: { 'source-highlighter' => 'rouge' }
+ assert_css 'table.linenotable', output, 1
+ assert_css 'table.linenotable td.linenos', output, 1
+ assert_css 'table.linenotable td.linenos pre.lineno', output, 1
+ assert_css 'table.linenotable td.code', output, 1
+ assert_css 'table.linenotable td.code pre:not([class])', output, 1
+ assert_xpath %(//pre[@class="lineno"][text()=" 9\n10\n"]), output, 1
+ end
+
+ test 'should restore callout marks to correct lines' do
+ ['', '%linenums'].each do |opts|
+ input = <<~EOS
+ :source-highlighter: rouge
+
+ [source#{opts},ruby]
+ ----
+ require 'rouge' # <1>
+
+ html = Rouge::Formatters::HTML.new.format(Rouge::Lexers::Ruby.new.lex('puts "Hello, world!"')) # <2>
+ puts html # <3> <4>
+ exit 0 # <5><6>
+ ----
+ <1> Load library
+ <2> Highlight source
+ <3> Print to stdout
+ <4> Redirect to a file to capture output
+ <5> Exit program
+ <6> Reports success
+ EOS
+ output = convert_string_to_embedded input, safe: :safe
+ assert_match(/<span class="s1">'rouge'<\/span>.* # <b class="conum">\(1\)<\/b>$/, output)
+ assert_match(/<span class="s1">'puts "Hello, world!"'<\/span>.* # <b class="conum">\(2\)<\/b>$/, output)
+ assert_match(/<span class="n">html<\/span>.* # <b class="conum">\(3\)<\/b> <b class="conum">\(4\)<\/b>$/, output)
+ # NOTE notice there's a newline before the closing </pre> tag when linenums are enabled
+ assert_match(/<span class="mi">0<\/span>.* # <b class="conum">\(5\)<\/b> <b class="conum">\(6\)<\/b>#{opts == '%linenums' ? ?\n : '</code>'}<\/pre>/, output)
+ end
+ end
+
+ test 'should line highlight specified lines when last line is not highlighted' do
+ ['', '%linenums'].each do |opts|
+ input = <<~EOS
+ :source-highlighter: rouge
+
+ [source#{opts},ruby,highlight=1]
+ ----
+ puts 'Hello, world!'
+ puts 'Goodbye, world!'
+ ----
+ EOS
+ # NOTE notice the newline in inside the closing </span> of the highlight span
+ expected = <<~EOS.chomp
+ <span class="hll"><span class="nb">puts</span> <span class="s1">'Hello, world!'</span>
+ </span><span class="nb">puts</span> <span class="s1">'Goodbye, world!'</span>#{opts == '%linenums' ? ?\n : '</code>'}</pre>
+ EOS
+
+ output = convert_string_to_embedded input, safe: :safe
+ assert_includes output, expected
+ end
+ end
+
+ test 'should line highlight specified lines when last line is highlighted' do
+ ['', '%linenums'].each do |opts|
+ input = <<~EOS
+ :source-highlighter: rouge
+
+ [source#{opts},ruby,highlight=2]
+ ----
+ puts 'Hello, world!'
+ puts 'Goodbye, world!'
+ ----
+ EOS
+ # NOTE notice the newline in inside the closing </span> of the highlight span
+ expected = <<~EOS.chomp
+ <span class="nb">puts</span> <span class="s1">'Hello, world!'</span>
+ <span class="hll"><span class="nb">puts</span> <span class="s1">'Goodbye, world!'</span>
+ </span>#{opts == '%linenums' ? '' : '</code>'}</pre>
+ EOS
+
+ output = convert_string_to_embedded input, safe: :safe
+ assert_includes output, expected
+ end
+ end
+
+ test 'should restore callout marks to correct lines if line numbering and line highlighting are enabled' do
+ [1, 2].each do |highlight|
+ input = <<~EOS
+ :source-highlighter: rouge
+
+ [source%linenums,ruby,highlight=#{highlight}]
+ ----
+ require 'rouge' # <1>
+ exit 0 # <2>
+ ----
+ <1> Load library
+ <2> Exit program
+ EOS
+ output = convert_string_to_embedded input, safe: :safe
+ assert_match(/<span class="s1">'rouge'<\/span>.* # <b class="conum">\(1\)<\/b>$/, output)
+ # NOTE notice there's a newline before the closing </pre> tag
+ assert_match(/<span class="mi">0<\/span>.* # <b class="conum">\(2\)<\/b>\n#{highlight == 2 ? '</span>' : ''}<\/pre>/, output)
+ end
+ end
+
+ test 'should gracefully fallback to default style if specified style not recognized' do
+ input = <<~'EOS'
+ :source-highlighter: rouge
+ :rouge-style: unknown
+
+ [source,ruby]
+ ----
+ puts 'Hello, world!'
+ ----
+ EOS
+ output = convert_string input, safe: :safe, linkcss_default: true
+ assert_css 'pre.rouge', output, 1
+ assert_includes output, 'pre.rouge .no {'
+ assert_match %r/pre\.rouge \{\s*background-color: #f8f8f8;/m, output
+ end
+
+ test 'should restore isolated callout mark on last line of source' do
+ input = <<~'EOS'
+ :source-highlighter: rouge
+
+ [source%linenums,ruby]
+ ----
+ require 'app'
+
+ launch_app
+ # <1>
+ ----
+ <1> Profit.
+ EOS
+
+ output = convert_string_to_embedded input, safe: :safe
+ # NOTE notice there's a newline before the closing </pre> tag, but not before the closing </td> tag
+ assert_match(/\n# <b class="conum">\(1\)<\/b>\n<\/pre><\/td>/, output)
+ end
+
+ test 'should number all lines when isolated callout mark is on last line of source and starting line number is set' do
+ input = <<~'EOS'
+ :source-highlighter: rouge
+
+ [source%linenums,ruby,start=5]
+ ----
+ require 'app'
+
+ launch_app
+ # <1>
+ ----
+ <1> Profit.
+ EOS
+
+ output = convert_string_to_embedded input, safe: :safe
+ assert_xpath %(//pre[@class="lineno"][text()="5\n6\n7\n8\n"]), output, 1
+ # NOTE notice there's a newline before the closing </pre> tag, but not before the closing </td> tag
+ assert_match(/\n# <b class="conum">\(1\)<\/b>\n<\/pre><\/td>/, output)
+ end
+ end
+
context 'Pygments' do
- test 'should highlight source if source-highlighter attribute is pygments' do
+ test 'should syntax highlight source if source-highlighter attribute is set' do
input = <<~'EOS'
:source-highlighter: pygments
:pygments-style: monokai
@@ -628,7 +843,27 @@ context 'Syntax Highlighter' do
assert_includes output, '.tok-c { color: #408080;'
end
- test 'should restore callout marks to correct lines if source highlighter is pygments and table line numbering is enabled' do
+ test 'should number lines if linenums option is set on source block' do
+ input = <<~'EOS'
+ :source-highlighter: pygments
+
+ [source%linenums,ruby]
+ ----
+ puts 'Hello, World!'
+ puts 'Goodbye, World!'
+ ----
+ EOS
+ output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
+ assert_css 'table.linenotable', output, 1
+ assert_css 'table.linenotable td.linenos', output, 1
+ assert_css 'table.linenotable td.linenos .linenodiv', output, 1
+ assert_css 'table.linenotable td.linenos .linenodiv pre:not([class])', output, 1
+ assert_css 'table.linenotable td.code', output, 1
+ assert_css 'table.linenotable td.code pre:not([class])', output, 1
+ assert_xpath %(//*[@class="linenodiv"]/pre[text()="1\n2"]), output, 1
+ end
+
+ test 'should restore callout marks to correct lines if table line numbering is enabled' do
input = <<~'EOS'
:source-highlighter: pygments
:pygments-linenums-mode: table
@@ -652,7 +887,7 @@ context 'Syntax Highlighter' do
assert_match(/\(\)\)\).*<\/span> # <b class="conum">\(2\)<\/b> <b class="conum">\(3\)<\/b>$/, output)
end
- test 'should restore isolated callout mark on last line of source when source highlighter is pygments' do
+ test 'should restore isolated callout mark on last line of source' do
input = <<~'EOS'
:source-highlighter: pygments
@@ -711,8 +946,31 @@ context 'Syntax Highlighter' do
EOS
output = convert_string_to_embedded input, safe: :safe
+ assert_css 'table.linenotable', output, 0
+ assert_css 'pre', output, 1
assert_includes output, '<span class="lineno"> 1 </span>'
assert_includes output, '<span class="lineno">10 </span>'
end
+
+ test 'should line highlight specified lines' do
+ input = <<~'EOS'
+ :source-highlighter: pygments
+
+ [source,ruby,highlight=1..2]
+ ----
+ puts 'Hello, world!'
+ puts 'Goodbye, world!'
+ ----
+ EOS
+ # NOTE notice the newline is inside the closing </span> of the highlight span
+ expected = <<~'EOS'.chomp
+ <pre class="pygments highlight"><code data-lang="ruby"><span></span><span class="hll"><span class="tok-nb">puts</span> <span class="tok-s1">&#39;Hello, world!&#39;</span>
+ </span><span class="hll"><span class="tok-nb">puts</span> <span class="tok-s1">&#39;Goodbye, world!&#39;</span>
+ </span></code></pre>
+ EOS
+
+ output = convert_string_to_embedded input, safe: :safe
+ assert_includes output, expected
+ end
end if ENV['PYGMENTS']
end