Repository: facelessuser/pymdown-extensions

Issue:
Provide a way to completely override the underlying highlighter for JS highlighters
Currently, SuperFences highlighter uses Pygments, or it does a generic output for JS highlighters.

You can add custom fences for specific keywords, but if you want to completely plugin and replace the highlighter to work better for a specific JS highlighter (let's say to better control JS highlighter specific options) it just isn't possible.

Currently, SuperFences and InlineHilite go through a list of formatters, and if none of the user ones match, it defaults to the general purpose one that they use to do basic highlighting.  But if we were able to override that base formatter, you could plug in any highlighter formatter you wanted.  I think we could do this simply by putting in logic that if a user specifies the custom name value as `*`, it will overwrite the base formatter. Then you could have your own Prism specific output and could control line numbers or whatever. 

@gir-bot add T: feature

Discussion:
facelessuser: @gir-bot lgtm

Patch:
diff --git a/docs/src/dictionary/en-custom.txt b/docs/src/dictionary/en-custom.txt
index d83e2692..17e1f730 100644
--- a/docs/src/dictionary/en-custom.txt
+++ b/docs/src/dictionary/en-custom.txt
@@ -142,6 +142,7 @@ html
 indepth
 injectable
 inline
+inlines
 ish
 isn't
 jQuery
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index cac6a656..e023b979 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -14,6 +14,8 @@ Please see [Release Notes](./release.md#upgrading-to-70) for details on upgradin
 - **NEW**: Deprecate the SuperFences `highight_code` option. As SuperFences syntax has language classes built right in,
   disabling the `highlight_code` option makes little sense. While `highlight_code` is still accepted, it currently does
   nothing and will be removed at some future time.
+- **NEW**: If a custom fence (SuperFences) or inline (InlineHilite) is given the name of `*`, it will override the
+  default fence logic.
 
 ## 7.0b1
 
diff --git a/docs/src/markdown/extensions/inlinehilite.md b/docs/src/markdown/extensions/inlinehilite.md
index 151868f4..c479c632 100644
--- a/docs/src/markdown/extensions/inlinehilite.md
+++ b/docs/src/markdown/extensions/inlinehilite.md
@@ -74,10 +74,16 @@ where each dictionary defines a custom inline code block. The dictionaries requi
 
 Keys        | Description
 ----------- | -----------
-`name`      | The language name that is specified when using the fence in Markdown.
+`name`      | The language name that is specified when using the fence in Markdown. If given `*`, it will override the base inline logic, the default for all inline names not handled by other custom inlines.
 `class`     | The class name assigned to the HTML element when converting from Markdown to HTML.
 `format`    | A function that formats the HTML output. The function should return either an ElementTree object or a string as HTML.
 
+!!! new "New in 7.0b2"
+    Starting in 7.0b2, you can override the base inline logic (the syntax highlighter) by specifying the custom inline
+    with a name of `*`. This means that if an inline does not match any other custom inline, the default, fallback
+    inline would be handled by your custom `*` inline. This can be useful for tailoring a inline for a specific,
+    favorite JavaScript highlighter.
+
 ### Formatters
 
 In general, formatters take four parameters: the source found between the backticks, the specified language, the class
diff --git a/docs/src/markdown/extensions/superfences.md b/docs/src/markdown/extensions/superfences.md
index a033d854..7f16b0fb 100644
--- a/docs/src/markdown/extensions/superfences.md
+++ b/docs/src/markdown/extensions/superfences.md
@@ -528,11 +528,17 @@ dictionary defines a custom fence. The dictionaries requires the following keys:
 
 Keys        | Description
 ----------- | -----------
-`name`      | The language name that is specified when using the fence in Markdown.
+`name`      | The language name that is specified when using the fence in Markdown. If given `*`, it will override the base fence logic, the default for all fence names not handled by other custom fences.
 `class`     | The class name assigned to the HTML element when converting from Markdown to HTML.
 `format`    | A function that formats the HTML output. The function should return a string as HTML.
 `validator` | An optional parameter that is used to provide a function to validate custom fence parameters.
 
+!!! new "New in 7.0b2"
+    Starting in 7.0b2, you can override the base fence logic (the syntax highlighter) by specifying the custom fence
+    with a name of `*`. This means that if a fence does not match any other custom fences, the default, fallback fence
+    would be handled by your custom `*` fence. This can be useful for tailoring a fence output with custom parameters
+    for a specific, favorite JavaScript highlighter.
+
 ### Formatters
 
 SuperFences provides two format functions by default, but you can always write your own:
diff --git a/pymdownx/inlinehilite.py b/pymdownx/inlinehilite.py
index bc439500..edb691b6 100644
--- a/pymdownx/inlinehilite.py
+++ b/pymdownx/inlinehilite.py
@@ -48,7 +48,7 @@ def _escape(txt):
 def _test(language, test_language=None):
     """Test language."""
 
-    return test_language is None or language == test_language
+    return test_language is None or test_language == '*' or language == test_language
 
 
 def _formatter(source, language, md, class_name="", fmt=None):
@@ -92,13 +92,16 @@ class InlineHilitePattern(InlineProcessor):
     def extend_custom_inline(self, name, formatter):
         """Extend SuperFences with the given name, language, and formatter."""
 
-        self.formatters.append(
-            {
-                "name": name,
-                "test": functools.partial(_test, test_language=name),
-                "formatter": formatter
-            }
-        )
+        obj = {
+            "name": name,
+            "test": functools.partial(_test, test_language=name),
+            "formatter": formatter
+        }
+
+        if name == '*':
+            self.formatters[0] = obj
+        else:
+            self.formatters.append(obj)
 
     def get_settings(self):
         """Check for CodeHilite extension and gather its settings."""
diff --git a/pymdownx/superfences.py b/pymdownx/superfences.py
index ed6ebea3..aaca1381 100644
--- a/pymdownx/superfences.py
+++ b/pymdownx/superfences.py
@@ -213,7 +213,7 @@ def _formatter(source, language, options, md, class_name="", _fmt=None, **kwargs
 def _test(language, test_language=None):
     """Test language."""
 
-    return test_language is None or language == test_language
+    return test_language is None or test_language == "*" or language == test_language
 
 
 class SuperFencesCodeExtension(Extension):
@@ -244,14 +244,17 @@ class SuperFencesCodeExtension(Extension):
     def extend_super_fences(self, name, formatter, validator):
         """Extend SuperFences with the given name, language, and formatter."""
 
-        self.superfences.append(
-            {
-                "name": name,
-                "test": functools.partial(_test, test_language=name),
-                "formatter": formatter,
-                "validator": validator
-            }
-        )
+        obj = {
+            "name": name,
+            "test": functools.partial(_test, test_language=name),
+            "formatter": formatter,
+            "validator": validator
+        }
+
+        if name == '*':
+            self.superfences[0] = obj
+        else:
+            self.superfences.append(obj)
 
     def extendMarkdown(self, md):
         """Add fenced block preprocessor to the Markdown instance."""
@@ -302,7 +305,8 @@ class SuperFencesCodeExtension(Extension):
         fenced = SuperFencesBlockPreprocessor(self.md)
         fenced.config = config
         fenced.extension = self
-        self.superfences[0]["formatter"] = fenced.highlight
+        if self.superfences[0]['name'] == "superfences":
+            self.superfences[0]["formatter"] = fenced.highlight
         self.md.preprocessors.register(fenced, "fenced_code_block", 25)
 
         indented_code = SuperFencesCodeBlockProcessor(self.md.parser)


Test patch:
diff --git a/tests/test_extensions/test_inlinehilite.py b/tests/test_extensions/test_inlinehilite.py
index d04aa773..48df4abf 100644
--- a/tests/test_extensions/test_inlinehilite.py
+++ b/tests/test_extensions/test_inlinehilite.py
@@ -10,6 +10,12 @@ def _format(src, language, class_name, md):
     return '<span class="lang-%s %s">%s</span>' % (language, class_name, src)
 
 
+def _default_format(src, language, class_name, md):
+    """Inline math formatter."""
+
+    return '<custom class="lang-%s %s">%s</custom>' % (language, class_name, src)
+
+
 class TestInlineHilite(util.MdCase):
     """Test general cases for inline highlight."""
 
@@ -372,3 +378,37 @@ class TestInlineHiliteCustom4(util.MdCase):
             r'`#!test src test`',
             r'<p><span class="lang-test class-test">src test</span></p>'
         )
+
+
+class TestInlineHiliteCustom5(util.MdCase):
+    """Test custom InlineHilite cases."""
+
+    extension = [
+        'pymdownx.highlight',
+        'pymdownx.inlinehilite',
+    ]
+    extension_configs = {
+        'pymdownx.inlinehilite': {
+            'css_class': 'inlinehilite',
+            'custom_inline': [
+                {
+                    'name': '*',
+                    'class': 'overwrite',
+                    'format': _default_format
+                },
+                {
+                    'name': 'test',
+                    'class': 'class-test',
+                    'format': _format
+                }
+            ]
+        }
+    }
+
+    def test_custom(self):
+        """Test custom formatter."""
+
+        self.check_markdown(
+            r'`#!test src test` `#!python src test`',
+            r'<p><span class="lang-test class-test">src test</span> <custom class="lang-python overwrite">src test</custom></p>'  # noqa: E501
+        )
diff --git a/tests/test_extensions/test_superfences.py b/tests/test_extensions/test_superfences.py
index a743c382..b216dc42 100644
--- a/tests/test_extensions/test_superfences.py
+++ b/tests/test_extensions/test_superfences.py
@@ -20,6 +20,12 @@ def custom_format(source, language, class_name, options, md, classes=None, value
     return '<div lang="%s" class_name="class-%s", option="%s">%s</div>' % (language, class_name, options['opt'], source)
 
 
+def default_format(source, language, class_name, options, md, classes=None, value_id='', **kwargs):
+    """Default format."""
+
+    return '<custom lang="%s" class_name="class-%s">%s</custom>' % (language, class_name, source)
+
+
 def custom_validator(language, options):
     """Custom validator."""
 
@@ -370,3 +376,48 @@ class TestSuperFencesCustom4(util.MdCase):
             ''',
             True
         )
+
+
+class TestSuperFencesCustom5(util.MdCase):
+    """Test Details."""
+
+    extension = ['pymdownx.superfences']
+    extension_configs = {
+        'pymdownx.superfences': {
+            'custom_fences': [
+                {
+                    'name': '*',
+                    'class': '',
+                    'format': default_format
+                },
+                {
+                    'name': 'math',
+                    'class': 'arithmatex',
+                    'format': arithmatex.fence_generic_format
+                }
+            ]
+        }
+    }
+
+    def test_default_override(self):
+        """Test default override."""
+
+        self.check_markdown(
+            r'''
+            ```math
+            E(\mathbf{v}, \mathbf{h}) = -\sum_{i,j}w_{ij}v_i h_j - \sum_i b_i v_i - \sum_j c_j h_j
+            ```
+
+            ```python
+            test
+            ```
+            ''',
+            r'''
+            <div class="arithmatex">\[
+            E(\mathbf{v}, \mathbf{h}) = -\sum_{i,j}w_{ij}v_i h_j - \sum_i b_i v_i - \sum_j c_j h_j
+            \]</div>
+
+            <p><custom lang="python" class_name="class-">test</custom></p>
+            ''',
+            True
+        )