mirror of
https://github.com/zulip/zulip.git
synced 2026-06-24 21:08:25 +08:00
This commit adds a Markdown tree-processor extension that renders
multi-line code blocks that are nested inside lists with the
formatting. Note that the code block could be nested inside multiple
list levels and would still get rendered correctly.
Tim: This fixes the need for unpleasant workarounds like
f5bfa4e793 and makes nested code blocks
in our documentation look exactly how users would expect them to.
75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
from markdown.extensions import Extension
|
|
from markdown.treeprocessors import Treeprocessor
|
|
from typing import Any, Dict, Optional, List, Tuple
|
|
import markdown
|
|
from xml.etree.cElementTree import Element
|
|
|
|
from zerver.lib.bugdown import walk_tree_with_family, ResultWithFamily
|
|
|
|
class NestedCodeBlocksRenderer(Extension):
|
|
def extendMarkdown(self, md: markdown.Markdown, md_globals: Dict[str, Any]) -> None:
|
|
md.treeprocessors.add(
|
|
'nested_code_blocks',
|
|
NestedCodeBlocksRendererTreeProcessor(md, self.getConfigs()),
|
|
'_end'
|
|
)
|
|
|
|
class NestedCodeBlocksRendererTreeProcessor(markdown.treeprocessors.Treeprocessor):
|
|
def __init__(self, md: markdown.Markdown, config: Dict[str, Any]) -> None:
|
|
super(NestedCodeBlocksRendererTreeProcessor, self).__init__(md)
|
|
|
|
def run(self, root: Element) -> None:
|
|
code_tags = walk_tree_with_family(root, self.get_code_tags)
|
|
nested_code_blocks = self.get_nested_code_blocks(code_tags)
|
|
for block in nested_code_blocks:
|
|
tag, text = block.result
|
|
codehilite_block = self.get_codehilite_block(text)
|
|
self.replace_element(block.family.grandparent,
|
|
codehilite_block,
|
|
block.family.parent)
|
|
|
|
def get_code_tags(self, e: Element) -> Optional[Tuple[str, Optional[str]]]:
|
|
if e.tag == "code":
|
|
return (e.tag, e.text)
|
|
return None
|
|
|
|
def get_nested_code_blocks(
|
|
self, code_tags: List[ResultWithFamily]
|
|
) -> List[ResultWithFamily]:
|
|
nested_code_blocks = []
|
|
for code_tag in code_tags:
|
|
parent = code_tag.family.parent # type: Any
|
|
grandparent = code_tag.family.grandparent # type: Any
|
|
if parent.tag == "p" and grandparent.tag == "li":
|
|
# if the parent (<p>) has no text, that means that the <code>
|
|
# element inside is its only child and thus, we can confidently
|
|
# say that this is a nested code block
|
|
if parent.text is None:
|
|
nested_code_blocks.append(code_tag)
|
|
|
|
return nested_code_blocks
|
|
|
|
def get_codehilite_block(self, code_block_text: str) -> Element:
|
|
div = markdown.util.etree.Element("div")
|
|
div.set("class", "codehilite")
|
|
pre = markdown.util.etree.SubElement(div, "pre")
|
|
pre.text = code_block_text
|
|
return div
|
|
|
|
def replace_element(
|
|
self, parent: Optional[Element],
|
|
replacement: markdown.util.etree.Element,
|
|
element_to_replace: Element
|
|
) -> None:
|
|
if parent is None:
|
|
return
|
|
|
|
children = parent.getchildren()
|
|
for index, child in enumerate(children):
|
|
if child is element_to_replace:
|
|
parent.insert(index, replacement)
|
|
parent.remove(element_to_replace)
|
|
|
|
def makeExtension(*args: Any, **kwargs: str) -> NestedCodeBlocksRenderer:
|
|
return NestedCodeBlocksRenderer(kwargs)
|