diff --git a/CHANGES.md b/CHANGES.md index 2dbc7b86fe..69622b4231 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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][] diff --git a/docs/api.rst b/docs/api.rst index 646b1d2f67..deb6f496c2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -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)`` diff --git a/docs/plugin-api.rst b/docs/plugin-api.rst index 83f3fa1ffd..4b9397ddf6 100644 --- a/docs/plugin-api.rst +++ b/docs/plugin-api.rst @@ -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}) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/highlight.js b/src/highlight.js index e14338e5e6..2b97470e3a 100644 --- a/src/highlight.js +++ b/src/highlight.js @@ -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) { @@ -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 @@ -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;