diff options
| author | Dan Allen <dan.j.allen@gmail.com> | 2019-02-03 14:20:00 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-03 14:20:00 -0700 |
| commit | cb16fea43fdec80569c34fd34acdcda65e2c71a1 (patch) | |
| tree | 4871b62af6fc8f41f3a161b43f6c7c0ea10e9bca | |
| parent | f8a490063bc45190458678bc492ab3a1c5b321b3 (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.adoc | 2 | ||||
| -rw-r--r-- | asciidoctor.gemspec | 2 | ||||
| -rw-r--r-- | data/stylesheets/asciidoctor-default.css | 11 | ||||
| -rw-r--r-- | lib/asciidoctor/rouge_ext.rb | 38 | ||||
| -rw-r--r-- | lib/asciidoctor/syntax_highlighter.rb | 8 | ||||
| -rw-r--r-- | lib/asciidoctor/syntax_highlighter/pygments.rb | 16 | ||||
| -rw-r--r-- | lib/asciidoctor/syntax_highlighter/rouge.rb | 126 | ||||
| -rw-r--r-- | test/syntax_highlighter_test.rb | 264 |
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">'Hello, world!'</span> + </span><span class="hll"><span class="tok-nb">puts</span> <span class="tok-s1">'Goodbye, world!'</span> + </span></code></pre> + EOS + + output = convert_string_to_embedded input, safe: :safe + assert_includes output, expected + end end if ENV['PYGMENTS'] end |
