Skip to content

Commit

Permalink
(parser) before/after plugin callbacks for highlight (#2395)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshgoebel committed Mar 3, 2020
1 parent 7bd13ce commit 2ca829e
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 3 deletions.
4 changes: 3 additions & 1 deletion CHANGES.md
Expand Up @@ -9,8 +9,10 @@ New themes:

- none.

Core Changes:
Parser Engine Changes:

- add `before:highlight` plugin API callback (#2395) [Josh Goebel][]
- add `after:highlight` plugin API callback (#2395) [Josh Goebel][]
- split out parse tree generation and HTML rendering concerns (#2404) [Josh Goebel][]
- every language can have a `name` attribute now (#2400) [Josh Goebel][]
- improve regular expression detect (less false-positives) (#2380) [Josh Goebel][]
Expand Down
1 change: 1 addition & 0 deletions docs/api.rst
Expand Up @@ -28,6 +28,7 @@ Returns an object with the following properties:
* ``value``: HTML string with highlighting markup
* ``top``: top of the current mode stack
* ``illegal``: boolean representing whether any illegal matches were found
* ``code``: the original raw code


``highlightAuto(code, languageSubset)``
Expand Down
40 changes: 40 additions & 0 deletions docs/plugin-api.rst
Expand Up @@ -61,6 +61,46 @@ This approach is best for simpler plugins.
Callbacks
---------

before:highlight({code, language})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This callback function is passed a context object with two keys:

code
The code to be highlighted.

language
The language grammar that should be used for highlighting.

Your plugin may modify either value and those new values will be used as input
to the highlighting engine. If you add a ``result`` key to the object that
result will be returned as the overall result and the internal highlighting code
will never even be called.

If you're plugin plans to make its own recursive calls to ``highlight`` you'll
need to manually handle this. Each time ``highlight`` is called your plugin
callbacks will also be called - making it easy to get into an infinite loop.
You'll likely need to use a class based plugin and add a guard so that your
plugin code is only triggered on the initial call to ``highlight`` and not on
any internal calls your plugin itself is making.

Note: This callback does not fire from highlighting resulting from auto-language detection.

It returns nothing.


after:highlight(result)
^^^^^^^^^^^^^^^^^^^^^^^

This callback function is passed the ``result`` object after highlighting is
complete. Your plugin may make any changes it desires to the result object
and that will be the final return value of the initial call to ``highlight``.

Note: This callback does not fire from highlighting resulting from auto-language detection.

It returns nothing.


after:highlightBlock({block, result})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
28 changes: 26 additions & 2 deletions src/highlight.js
Expand Up @@ -89,10 +89,34 @@ const HLJS = function(hljs) {
* @property {string} language - the language name
* @property {number} relevance - the relevance score
* @property {string} value - the highlighted HTML code
* @property {string} code - the original raw code
* @property {mode} top - top of the current mode stack
* @property {boolean} illegal - indicates whether any illegal matches were found
*/
function highlight(languageName, code, ignore_illegals, continuation) {
var context = {
code,
language: languageName
};
// the plugin can change the desired language or the code to be highlighted
// just be changing the object it was passed
fire("before:highlight", context);

// a before plugin can usurp the result completely by providing it's own
// in which case we don't even need to call highlight
var result = context.result ?
context.result :
_highlight(context.language, context.code, ignore_illegals, continuation);

result.code = context.code;
// the plugin can change anything in result to suite it
fire("after:highlight", result);

return result;
}

// private highlight that's used internally and does not fire callbacks
function _highlight(languageName, code, ignore_illegals, continuation) {
var codeToHighlight = code;

function endOfMode(mode, lexeme) {
Expand Down Expand Up @@ -157,7 +181,7 @@ const HLJS = function(hljs) {
}

var result = explicit ?
highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]) :
_highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]) :
highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : undefined);

// Counting embedded language score towards the host language may be disabled
Expand Down Expand Up @@ -402,7 +426,7 @@ const HLJS = function(hljs) {
};
var second_best = result;
languageSubset.filter(getLanguage).filter(autoDetection).forEach(function(name) {
var current = highlight(name, code, false);
var current = _highlight(name, code, false);
current.language = name;
if (current.relevance > second_best.relevance) {
second_best = current;
Expand Down

0 comments on commit 2ca829e

Please sign in to comment.