Added syntax highlighting to code blocks
Added syntax highlighting for code blocks using the pygments
library.
Implementation was pretty straight forward after getting the regex right to treat ‘=html’ blocks as raw.
Changes located in the run_jotdown
function and the new highlightcode_ function, as well as fiddling with css.
Click here to see changes to the build script
def highlight_code(code: str, language: str = None) -> str:
"""
Highlight code using Pygments with specified or guessed language.
"""
try:
if language:
lexer = get_lexer_by_name(language.lower())
else:
lexer = guess_lexer(code)
formatter = HtmlFormatter(
style=config["syntax_highlighting"]["style"],
linenos="table"
if config["syntax_highlighting"].get("line_numbers", False)
else False,
cssclass="highlight",
)
return highlight(code, lexer, formatter)
except ClassNotFound:
# If language isn't found, return code wrapped in pre tags
return f"<pre><code>{code}</code></pre>"
def run_jotdown(plaintext: str) -> str:
"""
Modified to handle code blocks with syntax highlighting.
Fixed to properly handle both raw HTML and HTML code blocks.
"""
CODE_BLOCK_RE = re.compile(r"\`\`\`\`*(=html|\s*(?:(\w+)\n))?(.*?)\`\`\`\`*", re.DOTALL)
code_blocks = []
marker_template = "§CODE_BLOCK_{}§"
def save_code_block(match):
raw_html_marker = match.group(1)
language = match.group(2)
code = match.group(3).strip()
# Check if this is a raw HTML block
if raw_html_marker == "=html":
return f"\`\`\`=html\n{code}\n\`\`\`"
# For all other cases, including 'html' language, highlight the code
highlighted = highlight_code(code, language)
marker = marker_template.format(len(code_blocks))
code_blocks.append(highlighted)
return f"\`\`\`=html\n{marker}\n\`\`\`"
# First, replace all code blocks with markers
processed_text = CODE_BLOCK_RE.sub(save_code_block, plaintext)
# Run through jotdown
html = run("jotdown", input=processed_text, text=True, capture_output=True).stdout
# Replace markers with actual highlighted code
for i, code in enumerate(code_blocks):
marker = marker_template.format(i)
html = html.replace(marker, code)
return html
Note that in the above snippet, the backticks (`) have been escaped to prevent the markup on this page from breaking, remove the backslashes if you use this code yourself.
Click here to see the CSS I settled on
:root {
--highlight-font-size: 0.9em;
--highlight-border-radius: 6px;
--highlight-padding: 1rem;
--highlight-line-number-color: #6e7681;
}
.highlight {
font-size: var(--highlight-font-size, 0.9em);
border-radius: var(--highlight-border-radius, 6px);
padding: var(--highlight-padding, 1rem);
}
.highlight pre {
margin: 0;
overflow-x: auto;
}
.highlight table {
border-spacing: 0;
border: none;
margin: 0;
}
.highlight table td {
padding: 0;
border: none;
}
.highlight .linenos {
user-select: none;
padding-right: 1rem;
color: var(--highlight-line-number-color);
text-align: right;
width: 1%;
white-space: nowrap;
}
@media (prefers-color-scheme: dark) {
.highlight {
background: var(--code-background, #262220) !important;
}
pre {
line-height: 125%;
}
td.linenos .normal {
color: #4e4e4e;
background-color: transparent;
padding-left: 5px;
padding-right: 5px;
}
span.linenos {
color: #4e4e4e;
background-color: transparent;
padding-left: 5px;
padding-right: 5px;
}
td.linenos .special {
color: #8f9494;
background-color: #ffffc0;
padding-left: 5px;
padding-right: 5px;
}
span.linenos.special {
color: #8f9494;
background-color: #ffffc0;
padding-left: 5px;
padding-right: 5px;
}
.highlight .hll {
background-color: #ddd0c0;
}
.highlight {
background: #262220;
color: #ddd0c0;
}
.highlight .c {
color: #70757a;
} /* Comment */
.highlight .err {
color: #af5f5f;
} /* Error */
.highlight .esc {
color: #ddd0c0;
} /* Escape */
.highlight .g {
color: #ddd0c0;
} /* Generic */
.highlight .k {
color: #919191;
} /* Keyword */
.highlight .l {
color: #af875f;
} /* Literal */
.highlight .n {
color: #ddd0c0;
} /* Name */
.highlight .o {
color: #878787;
} /* Operator */
.highlight .x {
color: #ddd0c0;
} /* Other */
.highlight .p {
color: #ddd0c0;
} /* Punctuation */
.highlight .ch {
color: #8f9f9f;
} /* Comment.Hashbang */
.highlight .cm {
color: #70757a;
} /* Comment.Multiline */
.highlight .cp {
color: #fdd0c0;
} /* Comment.Preproc */
.highlight .cpf {
color: #c9b98f;
} /* Comment.PreprocFile */
.highlight .c1 {
color: #70757a;
} /* Comment.Single */
.highlight .cs {
color: #af5f5f;
} /* Comment.Special */
.highlight .gd {
color: #bb6868;
} /* Generic.Deleted */
.highlight .ge {
color: #ddd0c0;
font-style: italic;
} /* Generic.Emph */
.highlight .ges {
color: #ddd0c0;
} /* Generic.EmphStrong */
.highlight .gr {
color: #af5f5f;
} /* Generic.Error */
.highlight .gh {
color: #ddd0c0;
} /* Generic.Heading */
.highlight .gi {
color: #849155;
} /* Generic.Inserted */
.highlight .go {
color: #ddd0c0;
} /* Generic.Output */
.highlight .gp {
color: #ddd0c0;
} /* Generic.Prompt */
.highlight .gs {
color: #ddd0c0;
font-weight: bold;
} /* Generic.Strong */
.highlight .gu {
color: #ddd0c0;
} /* Generic.Subheading */
.highlight .gt {
color: #af5f5f;
} /* Generic.Traceback */
.highlight .kc {
color: #875f5f;
} /* Keyword.Constant */
.highlight .kd {
color: #875f5f;
} /* Keyword.Declaration */
.highlight .kn {
color: #875f5f;
} /* Keyword.Namespace */
.highlight .kp {
color: #919191;
} /* Keyword.Pseudo */
.highlight .kr {
color: #b46276;
} /* Keyword.Reserved */
.highlight .kt {
color: #af875f;
} /* Keyword.Type */
.highlight .ld {
color: #af875f;
} /* Literal.Date */
.highlight .m {
color: #87afaf;
} /* Literal.Number */
.highlight .s {
color: #c9b98f;
} /* Literal.String */
.highlight .na {
color: #ddd0c0;
} /* Name.Attribute */
.highlight .nb {
color: #ddd0c0;
} /* Name.Builtin */
.highlight .nc {
color: #875f5f;
} /* Name.Class */
.highlight .no {
color: #af8787;
} /* Name.Constant */
.highlight .nd {
color: #fdd0c0;
} /* Name.Decorator */
.highlight .ni {
color: #ddd0c0;
} /* Name.Entity */
.highlight .ne {
color: #877575;
} /* Name.Exception */
.highlight .nf {
color: #fdd0c0;
} /* Name.Function */
.highlight .nl {
color: #ddd0c0;
} /* Name.Label */
.highlight .nn {
color: #ddd0c0;
} /* Name.Namespace */
.highlight .nx {
color: #ddd0c0;
} /* Name.Other */
.highlight .py {
color: #dfaf87;
} /* Name.Property */
.highlight .nt {
color: #87afaf;
} /* Name.Tag */
.highlight .nv {
color: #ddd0c0;
} /* Name.Variable */
.highlight .ow {
color: #878787;
} /* Operator.Word */
.highlight .pm {
color: #ddd0c0;
} /* Punctuation.Marker */
.highlight .w {
color: #ddd0c0;
} /* Text.Whitespace */
.highlight .mb {
color: #87afaf;
} /* Literal.Number.Bin */
.highlight .mf {
color: #87afaf;
} /* Literal.Number.Float */
.highlight .mh {
color: #87afaf;
} /* Literal.Number.Hex */
.highlight .mi {
color: #87afaf;
} /* Literal.Number.Integer */
.highlight .mo {
color: #87afaf;
} /* Literal.Number.Oct */
.highlight .sa {
color: #dfaf87;
} /* Literal.String.Affix */
.highlight .sb {
color: #c9b98f;
} /* Literal.String.Backtick */
.highlight .sc {
color: #c9b98f;
} /* Literal.String.Char */
.highlight .dl {
color: #c9b98f;
} /* Literal.String.Delimiter */
.highlight .sd {
color: #878787;
} /* Literal.String.Doc */
.highlight .s2 {
color: #c9b98f;
} /* Literal.String.Double */
.highlight .se {
color: #af5f5f;
} /* Literal.String.Escape */
.highlight .sh {
color: #c9b98f;
} /* Literal.String.Heredoc */
.highlight .si {
color: #af5f5f;
} /* Literal.String.Interpol */
.highlight .sx {
color: #fdd0c0;
} /* Literal.String.Other */
.highlight .sr {
color: #af5f5f;
} /* Literal.String.Regex */
.highlight .s1 {
color: #c9b98f;
} /* Literal.String.Single */
.highlight .ss {
color: #af5f5f;
} /* Literal.String.Symbol */
.highlight .bp {
color: #87afaf;
} /* Name.Builtin.Pseudo */
.highlight .fm {
color: #fdd0c0;
} /* Name.Function.Magic */
.highlight .vc {
color: #ddd0c0;
} /* Name.Variable.Class */
.highlight .vg {
color: #ddd0c0;
} /* Name.Variable.Global */
.highlight .vi {
color: #ddd0c0;
} /* Name.Variable.Instance */
.highlight .vm {
color: #ddd0c0;
} /* Name.Variable.Magic */
.highlight .il {
color: #87afaf;
} /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight {
background: var(--code-background, #e7daca) !important;
}
pre {
line-height: 125%;
}
td.linenos .normal {
color: inherit;
background-color: transparent;
padding-left: 5px;
padding-right: 5px;
}
span.linenos {
color: inherit;
background-color: transparent;
padding-left: 5px;
padding-right: 5px;
}
td.linenos .special {
color: #000000;
background-color: #ffffc0;
padding-left: 5px;
padding-right: 5px;
}
span.linenos.special {
color: #000000;
background-color: #ffffc0;
padding-left: 5px;
padding-right: 5px;
}
.highlight .hll {
background-color: #ffffcc;
}
.highlight {
background: #ffffff;
}
.highlight .c {
color: #999988;
font-style: italic;
} /* Comment */
.highlight .err {
color: #a61717;
background-color: #e3d2d2;
} /* Error */
.highlight .k {
font-weight: bold;
} /* Keyword */
.highlight .o {
font-weight: bold;
} /* Operator */
.highlight .ch {
color: #999988;
font-style: italic;
} /* Comment.Hashbang */
.highlight .cm {
color: #999988;
font-style: italic;
} /* Comment.Multiline */
.highlight .cp {
color: #999999;
font-weight: bold;
} /* Comment.Preproc */
.highlight .cpf {
color: #999988;
font-style: italic;
} /* Comment.PreprocFile */
.highlight .c1 {
color: #999988;
font-style: italic;
} /* Comment.Single */
.highlight .cs {
color: #999999;
font-weight: bold;
font-style: italic;
} /* Comment.Special */
.highlight .gd {
color: #000000;
background-color: #ffdddd;
} /* Generic.Deleted */
.highlight .ge {
font-style: italic;
} /* Generic.Emph */
.highlight .ges {
font-weight: bold;
font-style: italic;
} /* Generic.EmphStrong */
.highlight .gr {
color: #aa0000;
} /* Generic.Error */
.highlight .gh {
color: #999999;
} /* Generic.Heading */
.highlight .gi {
color: #000000;
background-color: #ddffdd;
} /* Generic.Inserted */
.highlight .go {
color: #888888;
} /* Generic.Output */
.highlight .gp {
color: #555555;
} /* Generic.Prompt */
.highlight .gs {
font-weight: bold;
} /* Generic.Strong */
.highlight .gu {
color: #aaaaaa;
} /* Generic.Subheading */
.highlight .gt {
color: #aa0000;
} /* Generic.Traceback */
.highlight .kc {
font-weight: bold;
} /* Keyword.Constant */
.highlight .kd {
font-weight: bold;
} /* Keyword.Declaration */
.highlight .kn {
font-weight: bold;
} /* Keyword.Namespace */
.highlight .kp {
font-weight: bold;
} /* Keyword.Pseudo */
.highlight .kr {
font-weight: bold;
} /* Keyword.Reserved */
.highlight .kt {
color: #445588;
font-weight: bold;
} /* Keyword.Type */
.highlight .m {
color: #009999;
} /* Literal.Number */
.highlight .s {
color: #bb8844;
} /* Literal.String */
.highlight .na {
color: #008080;
} /* Name.Attribute */
.highlight .nb {
color: #999999;
} /* Name.Builtin */
.highlight .nc {
color: #445588;
font-weight: bold;
} /* Name.Class */
.highlight .no {
color: #008080;
} /* Name.Constant */
.highlight .ni {
color: #800080;
} /* Name.Entity */
.highlight .ne {
color: #990000;
font-weight: bold;
} /* Name.Exception */
.highlight .nf {
color: #990000;
font-weight: bold;
} /* Name.Function */
.highlight .nn {
color: #555555;
} /* Name.Namespace */
.highlight .nt {
color: #000080;
} /* Name.Tag */
.highlight .nv {
color: #008080;
} /* Name.Variable */
.highlight .ow {
font-weight: bold;
} /* Operator.Word */
.highlight .w {
color: #bbbbbb;
} /* Text.Whitespace */
.highlight .mb {
color: #009999;
} /* Literal.Number.Bin */
.highlight .mf {
color: #009999;
} /* Literal.Number.Float */
.highlight .mh {
color: #009999;
} /* Literal.Number.Hex */
.highlight .mi {
color: #009999;
} /* Literal.Number.Integer */
.highlight .mo {
color: #009999;
} /* Literal.Number.Oct */
.highlight .sa {
color: #bb8844;
} /* Literal.String.Affix */
.highlight .sb {
color: #bb8844;
} /* Literal.String.Backtick */
.highlight .sc {
color: #bb8844;
} /* Literal.String.Char */
.highlight .dl {
color: #bb8844;
} /* Literal.String.Delimiter */
.highlight .sd {
color: #bb8844;
} /* Literal.String.Doc */
.highlight .s2 {
color: #bb8844;
} /* Literal.String.Double */
.highlight .se {
color: #bb8844;
} /* Literal.String.Escape */
.highlight .sh {
color: #bb8844;
} /* Literal.String.Heredoc */
.highlight .si {
color: #bb8844;
} /* Literal.String.Interpol */
.highlight .sx {
color: #bb8844;
} /* Literal.String.Other */
.highlight .sr {
color: #808000;
} /* Literal.String.Regex */
.highlight .s1 {
color: #bb8844;
} /* Literal.String.Single */
.highlight .ss {
color: #bb8844;
} /* Literal.String.Symbol */
.highlight .bp {
color: #999999;
} /* Name.Builtin.Pseudo */
.highlight .fm {
color: #990000;
font-weight: bold;
} /* Name.Function.Magic */
.highlight .vc {
color: #008080;
} /* Name.Variable.Class */
.highlight .vg {
color: #008080;
} /* Name.Variable.Global */
.highlight .vi {
color: #008080;
} /* Name.Variable.Instance */
.highlight .vm {
color: #008080;
} /* Name.Variable.Magic */
.highlight .il {
color: #009999;
} /* Literal.Number.Integer.Long */
}