diff --git a/markdown_it/rules_inline/escape.py b/markdown_it/rules_inline/escape.py index 0fca6c84..f2e2dd51 100644 --- a/markdown_it/rules_inline/escape.py +++ b/markdown_it/rules_inline/escape.py @@ -36,6 +36,17 @@ def escape(state: StateInline, silent: bool) -> bool: state.pos = pos return True + # '\' before a space is a literal backslash. Don't consume the space, so a + # trailing two-space hard line break is still detected by the newline rule. + if ch1 == " ": + if not silent: + token = state.push("text_special", "", 0) + token.content = "\\" + token.markup = "\\" + token.info = "escape" + state.pos = pos + return True + escapedStr = state.src[pos] if ch1_ord >= 0xD800 and ch1_ord <= 0xDBFF and pos + 1 < maximum: diff --git a/tests/test_port/test_misc.py b/tests/test_port/test_misc.py index 62b5bf85..550359bd 100644 --- a/tests/test_port/test_misc.py +++ b/tests/test_port/test_misc.py @@ -42,3 +42,11 @@ def type_filter(tokens, type_): assert tokens[2].markup == "." assert tokens[3].info == "199" assert tokens[3].markup == "." + + +def test_backslash_before_two_space_hardbreak(): + # A literal backslash directly before a two-space hard line break must not + # consume a space, otherwise only one space is left and the newline rule + # produces a soft break instead of a hard break. + md = MarkdownIt("commonmark") + assert md.render("foo\\ \nbar") == "
foo\\
\nbar