Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/mistune/renderers/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ._list import render_list, render_list_item

fenced_re = re.compile(r"^[`~]+", re.M)
_backtick_run_re = re.compile(r"`+")

#: leading markers that would be parsed as a new block (list, heading, block
#: quote) if they appear unescaped at the start of a line.
Expand Down Expand Up @@ -77,7 +78,17 @@ def image(self, token: Dict[str, Any], state: BlockState) -> str:
return "!" + self.link(token, state)

def codespan(self, token: Dict[str, Any], state: BlockState) -> str:
return "`" + cast(str, token["raw"]) + "`"
raw = cast(str, token["raw"])
# fence with one more backtick than the longest run in the content so
# an inner run can never be mistaken for the closing delimiter
longest = max((len(m.group()) for m in _backtick_run_re.finditer(raw)), default=0)
marker = "`" * (longest + 1)
# a re-parse strips one leading and trailing space when the content
# both begins and ends with a space (and is not all spaces); pad so a
# leading/trailing backtick or wrapping space survives that strip
if raw.strip() and (raw[0] == "`" or raw[-1] == "`" or (raw[0] == " " and raw[-1] == " ")):
raw = " " + raw + " "
return marker + raw + marker

def linebreak(self, token: Dict[str, Any], state: BlockState) -> str:
return " \n"
Expand Down
12 changes: 12 additions & 0 deletions tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ def test_escaped_marker_inside_list_item(self):
def test_escaped_backtick(self):
self.assert_round_trip(r"\`not code\`" + "\n")

def test_codespan_with_backticks(self):
# a code span whose content contains backticks (or is wrapped in
# spaces) must be re-fenced so the content survives a re-parse
for text in (
"``a`b``\n",
"`` `code` ``\n",
"`` ` ``\n",
"` x `\n",
"```a``b```\n",
):
self.assert_round_trip(text)

def test_real_markers_preserved(self):
for text in (
"* bullet\n",
Expand Down
Loading