Browse Source

Adjust a few aspects of syntax highlighting

Some (mostly minor) adjustments to the syntax highlighting:

* Take advantage of SCSS nesting
* Switch to using a <code> tag instead of a <div> (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)
merge-requests/40/head
Deimos 6 years ago
parent
commit
08d44610e3
  1. 282
      tildes/scss/_themes.scss
  2. 59
      tildes/tildes/lib/markdown.py

282
tildes/scss/_themes.scss

@ -39,7 +39,7 @@
background-color: $background-alt-color; background-color: $background-alt-color;
color: $text-color; color: $text-color;
@include syntaxhighlighting($is-light);
@include syntax-highlighting($is-light);
blockquote { blockquote {
background-color: $background-alt-color; background-color: $background-alt-color;
@ -290,147 +290,149 @@
} }
} }
@mixin syntaxhighlighting($is-light){
@mixin syntax-highlighting($is-light) {
@if ($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 { } @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; }
}
} }
} }

59
tildes/tildes/lib/markdown.py

@ -5,6 +5,7 @@
import re import re
from typing import ( from typing import (
Any,
Callable, Callable,
Dict, Dict,
Iterator, Iterator,
@ -50,25 +51,21 @@ from .cmark import (
def allow_syntax_highlighting_classes(tag: str, name: str, value: str) -> bool: def allow_syntax_highlighting_classes(tag: str, name: str, value: str) -> bool:
"""Allow all CSS classes from Pygments. """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 <code class="highlight">, as well as many <span> 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 <span> and <code> 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-<language', for example 'language-csharp'.
"""
if tag == "code":
# allow class attribute with a single class of exactly "highlight"
return name == "class" and value == "highlight"
return (" " not in value) and (tag == "code" and value.startswith("language-"))
return False
HTML_TAG_WHITELIST = ( HTML_TAG_WHITELIST = (
@ -78,7 +75,6 @@ HTML_TAG_WHITELIST = (
"br", "br",
"code", "code",
"del", "del",
"div",
"em", "em",
"h1", "h1",
"h2", "h2",
@ -110,8 +106,7 @@ HTML_ATTRIBUTE_WHITELIST = {
"ol": ["start"], "ol": ["start"],
"td": ["align"], "td": ["align"],
"th": ["align"], "th": ["align"],
"code": allow_language_info_string,
"div": allow_syntax_highlighting_classes,
"code": allow_syntax_highlighting_classes,
"span": allow_syntax_highlighting_classes, "span": allow_syntax_highlighting_classes,
} }
PROTOCOL_WHITELIST = ("http", "https") PROTOCOL_WHITELIST = ("http", "https")
@ -222,6 +217,22 @@ def postprocess_markdown_html(html: str) -> str:
return html return html
class CodeHtmlFormatter(HtmlFormatter):
"""Custom Pygments HtmlFormatter to use a <code> tag.
The default HtmlFormatter in Pygments outputs the code inside a
<div class="highlight"><pre>...</pre></div> structure. This changes that to
<code class="highlight">...</code> instead (assumes a <pre> is already present).
"""
def wrap(self, source: Any, outfile: Any) -> Iterator[Tuple[int, str]]:
"""Wrap the highlighted tokens with the <code> tag."""
# pylint: disable=unused-argument
yield (0, '<code class="highlight">')
yield from source
yield (0, "</code>")
def apply_syntax_highlighting(html: str) -> str: def apply_syntax_highlighting(html: str) -> str:
"""Get all code blocks with defined info string in class and highlight them.""" """Get all code blocks with defined info string in class and highlight them."""
soup = BeautifulSoup(html, features="html5lib") 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 # Get all code blocks and for every code block that has info string
code_blocks = soup.find_all("code", class_=re.compile("^language-")) code_blocks = soup.find_all("code", class_=re.compile("^language-"))
for code_block in code_blocks: for code_block in code_blocks:
# Apply Pygments
language = code_block["class"][0].replace("language-", "") language = code_block["class"][0].replace("language-", "")
try: try:
lexer = get_lexer_by_name(language) lexer = get_lexer_by_name(language)
except ClassNotFound: except ClassNotFound:
continue continue
highlighted = highlight( 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 return html

Loading…
Cancel
Save