diff --git a/CHANGES.md b/CHANGES.md index 10aabd33..4fd21de2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## python-markdown2 2.5.6 (not yet released) +- [pull #775] Speed up documents with many indented code blocks by skipping the `` lookahead when the text has no `` - [pull #762] Speed up documents with many links by skipping the backslash-escape scan for text without a backslash - [pull #748] Speed up `_unescape_special_chars` by skipping text with no hashes and dropping the full-text comparison per pass (#635) - [pull #749] URL-encode destinations generated by the `link-patterns` extra (#55) diff --git a/lib/markdown2.py b/lib/markdown2.py index d6285404..a829e23a 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -2088,7 +2088,7 @@ def _html_class_str_from_tag(self, tag: str) -> str: @mark_stage(Stage.CODE_BLOCKS) def _do_code_blocks(self, text: str) -> str: """Process Markdown `
` blocks."""
-        code_block_re = re.compile(r'''
+        code_block_pat = r'''
             (?:\n\n|\A\n?)
             (               # $1 = the code block -- one or more lines, starting with a space/tab
               (?:
@@ -2097,11 +2097,13 @@ def _do_code_blocks(self, text: str) -> str:
               )+
             )
             ((?=^[ ]{0,%d}\S)|\Z)   # Lookahead for non-space at line-start, or end of doc
+            ''' % (self.tab_width, self.tab_width)
+        if '' in text:
             # Lookahead to make sure this block isn't already in a code block.
-            # Needed when syntax highlighting is being used.
-            (?!([^<]|<(/?)span)*\)
-            ''' % (self.tab_width, self.tab_width),
-            re.M | re.X)
+            # Needed when syntax highlighting is being used. It scans ahead
+            # after every block, so leave it out when it can't match.
+            code_block_pat += r'(?!([^<]|<(/?)span)*\)'
+        code_block_re = re.compile(code_block_pat, re.M | re.X)
         return code_block_re.sub(self._code_block_sub, text)
 
     # Rules for a code span:
diff --git a/test/test_redos.py b/test/test_redos.py
index 1866a391..1759dddb 100644
--- a/test/test_redos.py
+++ b/test/test_redos.py
@@ -66,6 +66,11 @@ def issue_metadata_list_redos():
     return "---\nitems:\n  - " + " " * 32000 + "\n---\n\n# hello\n"
 
 
+def many_code_blocks():
+    # not technically a redos: each code block used to rescan the rest of the text
+    return "".join(f"Paragraph {i}:\n\n    code {i}\n\n" for i in range(3000))
+
+
 # whack everything in a dict for easy lookup later on
 CASES = {
     fn.__name__: (fn, extras)
@@ -82,6 +87,7 @@ def issue_metadata_list_redos():
         (issue_707_spaced_attrs, None),
         (issue_metadata_redos, ['metadata']),
         (issue_metadata_list_redos, ['metadata']),
+        (many_code_blocks, None),
     ]
 }