Custom Syntax Highlighter Adapter
You can integrate additional syntax highlighters into Asciidoctor by implementing and registering a syntax highlighter adapter. You can either write a new adapter from scratch or you can extend and even replace one of the built-in adapters.
Create a new adapter
To implement a new adapter, you must create a class that extends the Asciidoctor::SyntaxHighlighter::Base class, register the adapter for a value of the source-highlighter
attribute, and implement the required methods.
Which methods are required depends on whether the adapter is for a client-side (runs in the browser) or build-time (runs when the document is converted) syntax highlighter.
Here’s an example of how to write and register a syntax highlighter adapter for the Prism.js syntax highlighting library.
Prism.js is a client-side syntax highlighter, meaning it runs in the browser.
That means the adapter only has to implement methods that pertain to client-side syntax highlighting, which include format
, docinfo?
, and docinfo
.
class PrismSyntaxHighlighter < Asciidoctor::SyntaxHighlighter::Base
register_for 'prism'
def format node, lang, opts
opts[:transform] = proc do |pre, code|
code['class'] = %(language-#{lang}) if lang
end
super
end
def docinfo? location
location == :footer
end
def docinfo location, doc, opts
base_url = doc.attr 'prismdir', %(#{opts[:cdn_base_url]}/prism/1.15.0)
slash = opts[:self_closing_tag_slash]
unless (theme_name = doc.attr 'prism-style', 'prism') == 'prism'
theme_name = %(prism-#{theme_name})
end
%(<link rel="stylesheet" href="#{base_url}/themes/#{theme_name}.min.css"#{slash}>
<script src="#{base_url}/prism.min.js"></script>
<script src="#{base_url}/components/prism-ruby.min.js"></script>)
end
end
Save this code to a file named prism-syntax-highlighter.rb.
Then, require this file when invoking Asciidoctor and set source-highlighter=prism
to activate it:
$ asciidoctor -r ./prism-syntax-highlighter -a source-highlighter=prism document.adoc
You can also define an adapter for a syntax highlighter that runs during conversion. We’ll look at doing that while also extending a built-in adapter.
Extend an existing adapter
Instead of creating a new adapter, you can customize a built-in adapter by extending it, overriding its behavior, and optionally replacing it.
To extend an adapter, you need to look up a reference to the built-in adapter by name using the Asciidoctor::SyntaxHighlighter.for method, create a class that extends it, register the adapter with a unique name (or the same name, if you want to replace it), and override any methods that provide the behavior you want to modify.
Here’s the basic template for customizing an existing adapter:
class CustomAdapter < (Asciidoctor::SyntaxHighlighter.for 'rouge')
register_for 'rouge'
# override methods go here
end
Let’s look at some examples of how to customize a built-in adapter.
docinfo
Let’s override the adapter for Pygments to prevent it from adding a stylesheet to the HTML (presumably because the styles will be provided by a different stylesheet).
class ExtendedPygmentsSyntaxHighlighter < (Asciidoctor::SyntaxHighlighter.for 'pygments')
register_for 'pygments'
def docinfo? location
false
end
end
Save this code to a file named extended-pygments-syntax-highlighter.rb.
Then, require this file when invoking Asciidoctor, setting source-highlighter=pygments
to activate it, as you would normally do:
$ asciidoctor -r ./extended-pygments-syntax-highlighter.rb -a source-highlighter=pygments document.adoc
If, instead, you wanted to modify the built-in adapter to honor the location of a custom stylesheet specified by the pygments-stylesheet
attribute, you can do so by extending the adapter and overriding the docinfo
method.
class ExtendedPygmentsSyntaxHighlighter < (Asciidoctor::SyntaxHighlighter.for 'pygments')
register_for :pygments
def docinfo location, doc, opts
stylesheet = doc.attr 'pygments-stylesheet', './pygments.css'
if opts[:linkcss]
slash = opts[:self_closing_tag_slash]
%(<link rel="stylesheet" href="#{stylesheet}"#{slash}>)
else
stylesheet = doc.normalize_system_path stylesheet
%(<style>
#{doc.read_asset stylesheet, label: 'stylesheet', normalize: true}
</style>)
end
end
end
If you want to decorate built-in behavior, you can invoke the super
method anywhere inside the method to delegate to the behavior provided by the built-in adapter.
highlight
Let’s say you always want lines to be numbered, regardless of the setting in the document.
You can do so by overriding the highlight
method, setting the :number_lines
key on the opts
argument, then delegating back to the built-in adapter using super
.
def highlight node, source, lang, opts
opts[:number_lines] = true
super
end
create_formatter (Rouge)
When using Rouge as the syntax highlighter, you can customize the formatter by overriding the create_formatter
method.
This allows you to add custom logic for handling certain tokens in the source language.
Let’s assume that you want to look for bare URLs in code comments and translate them into links (i.e., autolinks), just like in AsciiDoc.
You can do that by weaving extra logic into the formatter that looks for tokens in the Comment
category and applies a substitution to the value.
class ExtendedRougeSyntaxHighlighter < (Asciidoctor::SyntaxHighlighter.for 'rouge')
register_for 'rouge'
def create_formatter node, source, lang, opts
formatter = super
formatter.singleton_class.prepend (Module.new do
def safe_span tok, safe_val
if tok.token_chain[0].matches? ::Rouge::Token::Tokens::Comment
safe_val = safe_val.gsub(/https?:\/\/\S+/, '<a href="\&">\&</a>')
end
super
end
end)
formatter
end
end
Since the formatter has access to all the tokens in the code identified by the syntax highlighter, this technique opens up a lot of possibilities.
For example, you could look for the Type
token in the Keyword
category in Java code and create a link to the API docs.
The lang
argument to the create_formatter
method lets you know the source language (e.g., java
) to which the tokens belong.
To study the logic you may be interesting in overriding, browse the code for the built-in syntax highlighter adapters.