From 08d44610e3518a6ca0e7c02d268719191a52c0d0 Mon Sep 17 00:00:00 2001 From: Deimos Date: Thu, 20 Sep 2018 22:52:03 -0600 Subject: [PATCH] Adjust a few aspects of syntax highlighting Some (mostly minor) adjustments to the syntax highlighting: * Take advantage of SCSS nesting * Switch to using a tag instead of a
(requires creating a custom Pygments formatter class) * Change "syntax_" prefix to "syntax-" for consistency * Refactor Bleach attribute-checking function a bit and drop unnecessary one (the "language-" class gets dropped before sanitization) --- tildes/scss/_themes.scss | 282 +++++++++++++++++----------------- tildes/tildes/lib/markdown.py | 59 +++---- 2 files changed, 175 insertions(+), 166 deletions(-) diff --git a/tildes/scss/_themes.scss b/tildes/scss/_themes.scss index 1128df1..21f23ba 100644 --- a/tildes/scss/_themes.scss +++ b/tildes/scss/_themes.scss @@ -39,7 +39,7 @@ background-color: $background-alt-color; color: $text-color; - @include syntaxhighlighting($is-light); + @include syntax-highlighting($is-light); blockquote { background-color: $background-alt-color; @@ -290,147 +290,149 @@ } } -@mixin syntaxhighlighting($is-light){ +@mixin syntax-highlighting($is-light) { @if ($is-light) { - .highlight { background-color: #eeeeee; color: #31383a } - .highlight .syntax_c { color: #129b12 } /* Comment */ - .highlight .syntax_err { color: #333333 } /* Error */ - .highlight .syntax_g { color: #333333 } /* Generic */ - .highlight .syntax_k { color: #3329c2 } /* Keyword */ - .highlight .syntax_l { color: #333333 } /* Literal */ - .highlight .syntax_n { color: #333333 } /* Name */ - .highlight .syntax_o { color: #859900 } /* Operator */ - .highlight .syntax_x { color: #cb4b16 } /* Other */ - .highlight .syntax_p { color: #333333 } /* Punctuation */ - .highlight .syntax_cm { color: #129b12 } /* Comment.Multiline */ - .highlight .syntax_cp { color: #859900 } /* Comment.Preproc */ - .highlight .syntax_c1 { color: #129b12 } /* Comment.Single */ - .highlight .syntax_cs { color: #859900 } /* Comment.Special */ - .highlight .syntax_gd { color: #2aa198 } /* Generic.Deleted */ - .highlight .syntax_ge { color: #333333; font-style: italic } /* Generic.Emph */ - .highlight .syntax_gr { color: #dc322f } /* Generic.Error */ - .highlight .syntax_gh { color: #cb4b16 } /* Generic.Heading */ - .highlight .syntax_gi { color: #859900 } /* Generic.Inserted */ - .highlight .syntax_go { color: #333333 } /* Generic.Output */ - .highlight .syntax_gp { color: #333333 } /* Generic.Prompt */ - .highlight .syntax_gs { color: #333333; font-weight: bold } /* Generic.Strong */ - .highlight .syntax_gu { color: #cb4b16 } /* Generic.Subheading */ - .highlight .syntax_gt { color: #333333 } /* Generic.Traceback */ - .highlight .syntax_kc { color: #cb4b16 } /* Keyword.Constant */ - .highlight .syntax_kd { color: #268bd2 } /* Keyword.Declaration */ - .highlight .syntax_kn { color: #859900 } /* Keyword.Namespace */ - .highlight .syntax_kp { color: #859900 } /* Keyword.Pseudo */ - .highlight .syntax_kr { color: #268bd2 } /* Keyword.Reserved */ - .highlight .syntax_kt { color: #3329c2 } /* Keyword.Type */ - .highlight .syntax_ld { color: #333333 } /* Literal.Date */ - .highlight .syntax_m { color: #2aa198 } /* Literal.Number */ - .highlight .syntax_s { color: #2aa198 } /* Literal.String */ - .highlight .syntax_na { color: #333333 } /* Name.Attribute */ - .highlight .syntax_nb { color: #B58900 } /* Name.Builtin */ - .highlight .syntax_nc { color: #268bd2 } /* Name.Class */ - .highlight .syntax_no { color: #cb4b16 } /* Name.Constant */ - .highlight .syntax_nd { color: #268bd2 } /* Name.Decorator */ - .highlight .syntax_ni { color: #cb4b16 } /* Name.Entity */ - .highlight .syntax_ne { color: #cb4b16 } /* Name.Exception */ - .highlight .syntax_nf { color: #268bd2 } /* Name.Function */ - .highlight .syntax_nl { color: #333333 } /* Name.Label */ - .highlight .syntax_nn { color: #333333 } /* Name.Namespace */ - .highlight .syntax_nx { color: #333333 } /* Name.Other */ - .highlight .syntax_py { color: #333333 } /* Name.Property */ - .highlight .syntax_nt { color: #268bd2 } /* Name.Tag */ - .highlight .syntax_nv { color: #268bd2 } /* Name.Variable */ - .highlight .syntax_ow { color: #859900 } /* Operator.Word */ - .highlight .syntax_w { color: #333333 } /* Text.Whitespace */ - .highlight .syntax_mf { color: #2aa198 } /* Literal.Number.Float */ - .highlight .syntax_mh { color: #2aa198 } /* Literal.Number.Hex */ - .highlight .syntax_mi { color: #2aa198 } /* Literal.Number.Integer */ - .highlight .syntax_mo { color: #2aa198 } /* Literal.Number.Oct */ - .highlight .syntax_sb { color: #129b12 } /* Literal.String.Backtick */ - .highlight .syntax_sc { color: #2aa198 } /* Literal.String.Char */ - .highlight .syntax_sd { color: #333333 } /* Literal.String.Doc */ - .highlight .syntax_s2 { color: #2aa198 } /* Literal.String.Double */ - .highlight .syntax_se { color: #cb4b16 } /* Literal.String.Escape */ - .highlight .syntax_sh { color: #333333 } /* Literal.String.Heredoc */ - .highlight .syntax_si { color: #2aa198 } /* Literal.String.Interpol */ - .highlight .syntax_sx { color: #2aa198 } /* Literal.String.Other */ - .highlight .syntax_sr { color: #dc322f } /* Literal.String.Regex */ - .highlight .syntax_s1 { color: #2aa198 } /* Literal.String.Single */ - .highlight .syntax_ss { color: #2aa198 } /* Literal.String.Symbol */ - .highlight .syntax_bp { color: #268bd2 } /* Name.Builtin.Pseudo */ - .highlight .syntax_vc { color: #268bd2 } /* Name.Variable.Class */ - .highlight .syntax_vg { color: #268bd2 } /* Name.Variable.Global */ - .highlight .syntax_vi { color: #268bd2 } /* Name.Variable.Instance */ - .highlight .syntax_il { color: #2aa198 } /* Literal.Number.Integer.Long */ + .highlight { + .syntax-c { color: #129b12; } // Comment + .syntax-err { color: #333333; } // Error + .syntax-g { color: #333333; } // Generic + .syntax-k { color: #3329c2; } // Keyword + .syntax-l { color: #333333; } // Literal + .syntax-n { color: #333333; } // Name + .syntax-o { color: #859900; } // Operator + .syntax-x { color: #cb4b16; } // Other + .syntax-p { color: #333333; } // Punctuation + .syntax-cm { color: #129b12; } // Comment.Multiline + .syntax-cp { color: #859900; } // Comment.Preproc + .syntax-c1 { color: #129b12; } // Comment.Single + .syntax-cs { color: #859900; } // Comment.Special + .syntax-gd { color: #2aa198; } // Generic.Deleted + .syntax-ge { color: #333333; font-style: italic; } // Generic.Emph + .syntax-gr { color: #dc322f; } // Generic.Error + .syntax-gh { color: #cb4b16; } // Generic.Heading + .syntax-gi { color: #859900; } // Generic.Inserted + .syntax-go { color: #333333; } // Generic.Output + .syntax-gp { color: #333333; } // Generic.Prompt + .syntax-gs { color: #333333; font-weight: bold; } // Generic.Strong + .syntax-gu { color: #cb4b16; } // Generic.Subheading + .syntax-gt { color: #333333; } // Generic.Traceback + .syntax-kc { color: #cb4b16; } // Keyword.Constant + .syntax-kd { color: #268bd2; } // Keyword.Declaration + .syntax-kn { color: #859900; } // Keyword.Namespace + .syntax-kp { color: #859900; } // Keyword.Pseudo + .syntax-kr { color: #268bd2; } // Keyword.Reserved + .syntax-kt { color: #3329c2; } // Keyword.Type + .syntax-ld { color: #333333; } // Literal.Date + .syntax-m { color: #2aa198; } // Literal.Number + .syntax-s { color: #2aa198; } // Literal.String + .syntax-na { color: #333333; } // Name.Attribute + .syntax-nb { color: #B58900; } // Name.Builtin + .syntax-nc { color: #268bd2; } // Name.Class + .syntax-no { color: #cb4b16; } // Name.Constant + .syntax-nd { color: #268bd2; } // Name.Decorator + .syntax-ni { color: #cb4b16; } // Name.Entity + .syntax-ne { color: #cb4b16; } // Name.Exception + .syntax-nf { color: #268bd2; } // Name.Function + .syntax-nl { color: #333333; } // Name.Label + .syntax-nn { color: #333333; } // Name.Namespace + .syntax-nx { color: #333333; } // Name.Other + .syntax-py { color: #333333; } // Name.Property + .syntax-nt { color: #268bd2; } // Name.Tag + .syntax-nv { color: #268bd2; } // Name.Variable + .syntax-ow { color: #859900; } // Operator.Word + .syntax-w { color: #333333; } // Text.Whitespace + .syntax-mf { color: #2aa198; } // Literal.Number.Float + .syntax-mh { color: #2aa198; } // Literal.Number.Hex + .syntax-mi { color: #2aa198; } // Literal.Number.Integer + .syntax-mo { color: #2aa198; } // Literal.Number.Oct + .syntax-sb { color: #129b12; } // Literal.String.Backtick + .syntax-sc { color: #2aa198; } // Literal.String.Char + .syntax-sd { color: #333333; } // Literal.String.Doc + .syntax-s2 { color: #2aa198; } // Literal.String.Double + .syntax-se { color: #cb4b16; } // Literal.String.Escape + .syntax-sh { color: #333333; } // Literal.String.Heredoc + .syntax-si { color: #2aa198; } // Literal.String.Interpol + .syntax-sx { color: #2aa198; } // Literal.String.Other + .syntax-sr { color: #dc322f; } // Literal.String.Regex + .syntax-s1 { color: #2aa198; } // Literal.String.Single + .syntax-ss { color: #2aa198; } // Literal.String.Symbol + .syntax-bp { color: #268bd2; } // Name.Builtin.Pseudo + .syntax-vc { color: #268bd2; } // Name.Variable.Class + .syntax-vg { color: #268bd2; } // Name.Variable.Global + .syntax-vi { color: #268bd2; } // Name.Variable.Instance + .syntax-il { color: #2aa198; } // Literal.Number.Integer.Long + } } @else { - .highlight { background-color: #002b36; color: #93a1a1; } - .highlight .syntax_c { color: #759299; } - .highlight .syntax_err { color: #93a1a1; } - .highlight .syntax_g { color: #93a1a1; } - .highlight .syntax_k { color: #859900; } - .highlight .syntax_l { color: #93a1a1; } - .highlight .syntax_n { color: #93a1a1; } - .highlight .syntax_o { color: #859900; } - .highlight .syntax_x { color: #e9662f; } - .highlight .syntax_p { color: #93a1a1; } - .highlight .syntax_cm { color: #759299; } - .highlight .syntax_cp { color: #859900; } - .highlight .syntax_c1 { color: #759299; } - .highlight .syntax_cs { color: #859900; } - .highlight .syntax_gd { color: #2aa198; } - .highlight .syntax_ge { color: #93a1a1; font-style: italic; } - .highlight .syntax_gr { color: #e8625f; } - .highlight .syntax_gh { color: #e9662f; } - .highlight .syntax_gi { color: #859900; } - .highlight .syntax_go { color: #93a1a1; } - .highlight .syntax_gp { color: #93a1a1; } - .highlight .syntax_gs { color: #93a1a1; font-weight: bold; } - .highlight .syntax_gu { color: #e9662f; } - .highlight .syntax_gt { color: #93a1a1; } - .highlight .syntax_kc { color: #e9662f; } - .highlight .syntax_kd { color: #3294da; } - .highlight .syntax_kn { color: #859900; } - .highlight .syntax_kp { color: #859900; } - .highlight .syntax_kr { color: #3294da; } - .highlight .syntax_kt { color: #e8625f; } - .highlight .syntax_ld { color: #93a1a1; } - .highlight .syntax_m { color: #2aa198; } - .highlight .syntax_s { color: #2aa198; } - .highlight .syntax_na { color: #93a1a1; } - .highlight .syntax_nb { color: #B58900; } - .highlight .syntax_nc { color: #3294da; } - .highlight .syntax_no { color: #e9662f; } - .highlight .syntax_nd { color: #3294da; } - .highlight .syntax_ni { color: #e9662f; } - .highlight .syntax_ne { color: #e9662f; } - .highlight .syntax_nf { color: #3294da; } - .highlight .syntax_nl { color: #93a1a1; } - .highlight .syntax_nn { color: #93a1a1; } - .highlight .syntax_nx { color: #93a1a1; } - .highlight .syntax_py { color: #93a1a1; } - .highlight .syntax_nt { color: #3294da; } - .highlight .syntax_nv { color: #3294da; } - .highlight .syntax_ow { color: #859900; } - .highlight .syntax_w { color: #93a1a1; } - .highlight .syntax_mf { color: #2aa198; } - .highlight .syntax_mh { color: #2aa198; } - .highlight .syntax_mi { color: #2aa198; } - .highlight .syntax_mo { color: #2aa198; } - .highlight .syntax_sb { color: #759299; } - .highlight .syntax_sc { color: #2aa198; } - .highlight .syntax_sd { color: #93a1a1; } - .highlight .syntax_s2 { color: #2aa198; } - .highlight .syntax_se { color: #e9662f; } - .highlight .syntax_sh { color: #93a1a1; } - .highlight .syntax_si { color: #2aa198; } - .highlight .syntax_sx { color: #2aa198; } - .highlight .syntax_sr { color: #e8625f; } - .highlight .syntax_s1 { color: #2aa198; } - .highlight .syntax_ss { color: #2aa198; } - .highlight .syntax_bp { color: #3294da; } - .highlight .syntax_vc { color: #3294da; } - .highlight .syntax_vg { color: #3294da; } - .highlight .syntax_vi { color: #3294da; } - .highlight .syntax_il { color: #2aa198; } + .highlight { + .syntax-c { color: #759299; } + .syntax-err { color: #93a1a1; } + .syntax-g { color: #93a1a1; } + .syntax-k { color: #859900; } + .syntax-l { color: #93a1a1; } + .syntax-n { color: #93a1a1; } + .syntax-o { color: #859900; } + .syntax-x { color: #e9662f; } + .syntax-p { color: #93a1a1; } + .syntax-cm { color: #759299; } + .syntax-cp { color: #859900; } + .syntax-c1 { color: #759299; } + .syntax-cs { color: #859900; } + .syntax-gd { color: #2aa198; } + .syntax-ge { color: #93a1a1; font-style: italic; } + .syntax-gr { color: #e8625f; } + .syntax-gh { color: #e9662f; } + .syntax-gi { color: #859900; } + .syntax-go { color: #93a1a1; } + .syntax-gp { color: #93a1a1; } + .syntax-gs { color: #93a1a1; font-weight: bold; } + .syntax-gu { color: #e9662f; } + .syntax-gt { color: #93a1a1; } + .syntax-kc { color: #e9662f; } + .syntax-kd { color: #3294da; } + .syntax-kn { color: #859900; } + .syntax-kp { color: #859900; } + .syntax-kr { color: #3294da; } + .syntax-kt { color: #e8625f; } + .syntax-ld { color: #93a1a1; } + .syntax-m { color: #2aa198; } + .syntax-s { color: #2aa198; } + .syntax-na { color: #93a1a1; } + .syntax-nb { color: #B58900; } + .syntax-nc { color: #3294da; } + .syntax-no { color: #e9662f; } + .syntax-nd { color: #3294da; } + .syntax-ni { color: #e9662f; } + .syntax-ne { color: #e9662f; } + .syntax-nf { color: #3294da; } + .syntax-nl { color: #93a1a1; } + .syntax-nn { color: #93a1a1; } + .syntax-nx { color: #93a1a1; } + .syntax-py { color: #93a1a1; } + .syntax-nt { color: #3294da; } + .syntax-nv { color: #3294da; } + .syntax-ow { color: #859900; } + .syntax-w { color: #93a1a1; } + .syntax-mf { color: #2aa198; } + .syntax-mh { color: #2aa198; } + .syntax-mi { color: #2aa198; } + .syntax-mo { color: #2aa198; } + .syntax-sb { color: #759299; } + .syntax-sc { color: #2aa198; } + .syntax-sd { color: #93a1a1; } + .syntax-s2 { color: #2aa198; } + .syntax-se { color: #e9662f; } + .syntax-sh { color: #93a1a1; } + .syntax-si { color: #2aa198; } + .syntax-sx { color: #2aa198; } + .syntax-sr { color: #e8625f; } + .syntax-s1 { color: #2aa198; } + .syntax-ss { color: #2aa198; } + .syntax-bp { color: #3294da; } + .syntax-vc { color: #3294da; } + .syntax-vg { color: #3294da; } + .syntax-vi { color: #3294da; } + .syntax-il { color: #2aa198; } + } } } diff --git a/tildes/tildes/lib/markdown.py b/tildes/tildes/lib/markdown.py index 31e0513..60d25f5 100644 --- a/tildes/tildes/lib/markdown.py +++ b/tildes/tildes/lib/markdown.py @@ -5,6 +5,7 @@ import re from typing import ( + Any, Callable, Dict, Iterator, @@ -50,25 +51,21 @@ from .cmark import ( def allow_syntax_highlighting_classes(tag: str, name: str, value: str) -> bool: """Allow all CSS classes from Pygments. - These classes always begin with 'syntax_'. We need to allow - .highlight class as well, as Pygments use it to group syntax - highlighting classes. + Pygments will add a , as well as many tags around + elements of the code, with classes all starting with "syntax-". """ - return (" " not in value) and ( - (value.startswith("syntax_") and tag == "span") - or (value == "highlight" and tag == "div") - ) - + if tag not in ("span", "code"): + raise ValueError("This method only sanitizes and tags") -def allow_language_info_string(tag: str, name: str, value: str) -> bool: - """Allow language info strings on code tag. + if tag == "span": + # allow class attribute with a single class starting with "syntax-" + return name == "class" and " " not in value and value.startswith("syntax-") - Info string is the thing that you write after ``` markdown. - For example in ```csharp the info string will be 'csharp'. - The class is 'language- str: return html +class CodeHtmlFormatter(HtmlFormatter): + """Custom Pygments HtmlFormatter to use a tag. + + The default HtmlFormatter in Pygments outputs the code inside a +
...
structure. This changes that to + ... instead (assumes a
 is already present).
+    """
+
+    def wrap(self, source: Any, outfile: Any) -> Iterator[Tuple[int, str]]:
+        """Wrap the highlighted tokens with the  tag."""
+        # pylint: disable=unused-argument
+        yield (0, '')
+        yield from source
+        yield (0, "")
+
+
 def apply_syntax_highlighting(html: str) -> str:
     """Get all code blocks with defined info string in class and highlight them."""
     soup = BeautifulSoup(html, features="html5lib")
@@ -229,21 +240,17 @@ def apply_syntax_highlighting(html: str) -> str:
     # Get all code blocks and for every code block that has info string
     code_blocks = soup.find_all("code", class_=re.compile("^language-"))
     for code_block in code_blocks:
-        # Apply Pygments
         language = code_block["class"][0].replace("language-", "")
+
         try:
             lexer = get_lexer_by_name(language)
         except ClassNotFound:
             continue
+
         highlighted = highlight(
-            code_block.text,
-            lexer,
-            HtmlFormatter(
-                classprefix="syntax_"  # All highlight classes will be
-                # prefixed with 'syntax_'
-            ),
+            code_block.text, lexer, CodeHtmlFormatter(classprefix="syntax-")
         )
-        html = html.replace(str(code_block.parent), highlighted, 1)
+        html = html.replace(str(code_block), highlighted, 1)
 
     return html